ServiceCollection configuration using strings (config files) in .NET Core - c#

Is there a way to configure dependency injection in the standard Microsoft.Extensions.DependencyInjection.ServiceCollection library in .net core, without actually having a reference to the implementation classes in question? (to get implementation class names from configuration files?)
For example:
services.AddTransient<ISomething>("The.Actual.Thing");// Where The.Actual.Thing is a concrete class

If your really keen to use strings parameters to load objects on the fly, you can use a factory that creates dynamic objects.
public interface IDynamicTypeFactory
{
object New(string t);
}
public class DynamicTypeFactory : IDynamicTypeFactory
{
object IDynamicTypeFactory.New(string t)
{
var asm = Assembly.GetEntryAssembly();
var type = asm.GetType(t);
return Activator.CreateInstance(type);
}
}
Lets say you have the following service
public interface IClass
{
string Test();
}
public class Class1 : IClass
{
public string Test()
{
return "TEST";
}
}
You can then
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDynamicTypeFactory, DynamicTypeFactory>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IDynamicTypeFactory dynamicTypeFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
var t = (IClass)dynamicTypeFactory.New("WebApplication1.Class1");
await context.Response.WriteAsync(t.Test());
});
}

Related

ASPNET Core replacement for [Session] =

We are migrating from ASPNET MVC5 to ASPNET Core meaning we need to refactor some code.
We were using Session = model to store the model in the session, then retrieving it from another Controller.
We understand this option has been discontinued in Core.
We have attempted to use:
HttpContext.Session.SetString("Data", JsonConvert.SerializeObject(model));
However, when Deserialising by using:
var json = HttpContext.Session.GetString("Data");
var model = JsonConvert.DeserializeObject<SearchListViewModel>(json);
The resulting model does not come back the same - it is one long string rather than a structured list (which is was before Serialising).
Is there a better way to achieve passing a model from one controller to another?
To configure Session in your project, you need to do the following:
In your Startup.cs, under the Configure method, add this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSession();
}
And then under the ConfigureServices method, add this:
public void ConfigureServices(IServiceCollection services)
{
//Added for session state
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
}
Create a model class of your object type (whatever you want to store). I am giving a simple example:
public class SearchListViewModel
{
public int SearchID{ get; set; }
public string SearchName{ get; set; }
//so on
}
Then create a SessionExtension helper to set and retrieve your complex object as JSON:
public static class SessionExtensions
{
public static void SetObjectAsJson(this ISession session, string key, object value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetObjectFromJson<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
}
}
Then finally set the complex object in your session as:
var search= new SearchListViewModel();
search.SearchID= 1;
search.SearchName= "Test";
HttpContext.Session.SetObjectAsJson("SearchListViewModel", search);
To retrieve your complex object in your session:
var searhDetails = HttpContext.Session.GetObjectFromJson<SearchListViewModel>("SearchListViewModel");
int searchID= SearchListViewModel.SearchID;
string searchName= SearchListViewModel.SearchName;
you can still use session if you need. You just need to config it in startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSession();
....another your code
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSession();
... another your code
}

.net core AddSingleton initialization

I'm trying to register a singleton class, providing the constructor parameters in Startup.ConfigureServices method.
After several tries, I'm still not able to make the dbContext injection working
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddDbContext<EFContext>();
services.AddSingleton<OPCClient>(x =>
{
string endpointURL = "opc.tcp://xxx.yyy.zzz.nnn:12345";
bool autoAccept = false;
int stopTimeout = Timeout.Infinite;
var efContext = x.GetService<EFContext>();
OPCClient client = new OPCClient(endpointURL, autoAccept, stopTimeout, efContext);
client.Run();
return client;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// warmup
app.ApplicationServices.GetService<OPCClient>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<OPCService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
When var efContext = x.GetService<EFContext>(); is executed, I'm getting the exception
System.InvalidOperationException: 'Cannot resolve scoped service 'EFContext' from root provider.'
Thanks for any help in injecting the DbContext in OPCClient class
It is not a good choice to use a scoped service (the EFContext) inside a singleton.
The DI container creates a new instance of a scoped service for every request, while it creates a singleton only once and this can lead to inconsistent states for your objects. Documentation here
I suggest to change the lifetime of OPCClient to scoped - using services.AddScoped instead of services.AddSingleton. If you cannot do this, pass a reference of IServiceProvider rather than EFContext and resolve that service from the container each time you need to use it:
public class OPCClient
{
private IServicePrivder _serviceProvider;
public OPCClient (IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoSomething() {
EfContext efContext = _serviceProvider.GetRequiredService<EfContext>();
}
}

A suitable constructor for my type using DI could not be located

I have a .NET core WebAPI project that uses Hangfire for background jobs. I am trying to setup Simple Injector for DIs. My porject has an IFoo and a Foo class that looks as follows
public interface IFoo
{
void DoSomething();
}
public class Foo : IFoo
{
public Foo() { }
public void DoSomething()
{
Console.WriteLine($"Foo::DoSomething");
}
}
Below is how I setup the Simple Injector container. I am using Hangfire.SimpleInjector nuget package
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var container = new SimpleInjector.Container();
container.Register<IFoo, Foo>();
GlobalConfiguration.Configuration.UseActivator(
new Hangfire.SimpleInjector.SimpleInjectorJobActivator(container));
services.AddHangfire(x => x.UseSqlServerStorage(<My Connection string>));
services.AddHangfireServer();
services.AddControllers();
}
}
The background job is setup as following in controller
public IActionResult DoSomething()
{
var jobID = BackgroundJob.Enqueue<IFoo>( x => x.DoSomething());
return Ok();
}
But this job fails with following stack trace.
An exception occurred during processing of a background job.
System.InvalidOperationException A suitable constructor for type
'MyWebAPI.Controllers.IFoo' could not be located. Ensure the type is
concrete and services are registered for all parameters of a public
constructor.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider, Type, Object[])
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(IServiceProvider, Type)
at Hangfire.AspNetCore.AspNetCoreJobActivatorScope.Resolve(Type type)
at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.<PerformJobWithFilters>b__0()
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter, PerformingContext, Func`1)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_1.<PerformJobWithFilters>b__2()
at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext, IEnumerable`1)
at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext, IStorageConnection, String)
What am I doing wrong in setting all this up?
I am not sure the DI in Hangfire is for this purpose.
You need dependency injection to resolve inner dependencies,
not to resolve the main type you want to use.
You can check the documentation here.
Check this answer with same problem.

Trying to get injected service for seeding data in ASP.NET core MVC

In ConfigureServices I say
services.AddDbContext<PwdrsDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("PwdrsDbConnection"));
});
RegisterServices(services);
In configure I say
SeedData.SeedDatabase(app);
In the static seed method I say
public class SeedData
{
public static void SeedDatabase(IApplicationBuilder app)
{
PwdrsDbContext context = app.ApplicationServices.GetService<PwdrsDbContext>();
}
}
and when I run it says
Cannot resolve scoped service 'Pwdrs.Infra.Data.Context.PwdrsDbContext' from root provider
I need the dbcontext to seed the data but what am I missing?
You need to inject the services from your ConfigureServices method into the Configure method separately:
public void Configure(
IApplicationBuilder app,
IServiceProvider services) // <- magic here
{
// ...
SeedData.SeedDatabase(services);
}
public class SeedData
{
public static void SeedDatabase(IServiceProvider services)
{
PwdrsDbContext context = services.GetRequiredService<PwdrsDbContext>();
}
}

How to get HttpContext.Current in ASP.NET Core? [duplicate]

This question already has answers here:
Access the current HttpContext in ASP.NET Core
(7 answers)
Closed 6 years ago.
We are currently rewriting/converting our ASP.NET WebForms application using ASP.NET Core. Trying to avoid re-engineering as much as possible.
There is a section where we use HttpContext in a class library to check the current state. How can I access HttpContext.Current in .NET Core 1.0?
var current = HttpContext.Current;
if (current == null)
{
// do something here
// string connection = Configuration.GetConnectionString("MyDb");
}
I need to access this in order to construct current application host.
$"{current.Request.Url.Scheme}://{current.Request.Url.Host}{(current.Request.Url.Port == 80 ? "" : ":" + current.Request.Url.Port)}";
As a general rule, converting a Web Forms or MVC5 application to ASP.NET Core will require a significant amount of refactoring.
HttpContext.Current was removed in ASP.NET Core. Accessing the current HTTP context from a separate class library is the type of messy architecture that ASP.NET Core tries to avoid. There are a few ways to re-architect this in ASP.NET Core.
HttpContext property
You can access the current HTTP context via the HttpContext property on any controller. The closest thing to your original code sample would be to pass HttpContext into the method you are calling:
public class HomeController : Controller
{
public IActionResult Index()
{
MyMethod(HttpContext);
// Other code
}
}
public void MyMethod(Microsoft.AspNetCore.Http.HttpContext context)
{
var host = $"{context.Request.Scheme}://{context.Request.Host}";
// Other code
}
HttpContext parameter in middleware
If you're writing custom middleware for the ASP.NET Core pipeline, the current request's HttpContext is passed into your Invoke method automatically:
public Task Invoke(HttpContext context)
{
// Do something with the current HTTP context...
}
HTTP context accessor
Finally, you can use the IHttpContextAccessor helper service to get the HTTP context in any class that is managed by the ASP.NET Core dependency injection system. This is useful when you have a common service that is used by your controllers.
Request this interface in your constructor:
public MyMiddleware(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
You can then access the current HTTP context in a safe way:
var context = _httpContextAccessor.HttpContext;
// Do something with the current HTTP context...
IHttpContextAccessor isn't always added to the service container by default, so register it in ConfigureServices just to be safe:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
// if < .NET Core 2.2 use this
//services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Other code...
}
Necromancing.
YES YOU CAN, and this is how.
A secret tip for those migrating large junks chunks of code:
The following method is an evil carbuncle of a hack which is actively engaged in carrying out the express work of satan (in the eyes of .NET Core framework developers), but it works:
In public class Startup
add a property
public IConfigurationRoot Configuration { get; }
And then add a singleton IHttpContextAccessor to DI in ConfigureServices.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
Then in Configure
public void Configure(
IApplicationBuilder app
,IHostingEnvironment env
,ILoggerFactory loggerFactory
)
{
add the DI Parameter IServiceProvider svp, so the method looks like:
public void Configure(
IApplicationBuilder app
,IHostingEnvironment env
,ILoggerFactory loggerFactory
,IServiceProvider svp)
{
Next, create a replacement class for System.Web:
namespace System.Web
{
namespace Hosting
{
public static class HostingEnvironment
{
public static bool m_IsHosted;
static HostingEnvironment()
{
m_IsHosted = false;
}
public static bool IsHosted
{
get
{
return m_IsHosted;
}
}
}
}
public static class HttpContext
{
public static IServiceProvider ServiceProvider;
static HttpContext()
{ }
public static Microsoft.AspNetCore.Http.HttpContext Current
{
get
{
// var factory2 = ServiceProvider.GetService<Microsoft.AspNetCore.Http.IHttpContextAccessor>();
object factory = ServiceProvider.GetService(typeof(Microsoft.AspNetCore.Http.IHttpContextAccessor));
// Microsoft.AspNetCore.Http.HttpContextAccessor fac =(Microsoft.AspNetCore.Http.HttpContextAccessor)factory;
Microsoft.AspNetCore.Http.HttpContext context = ((Microsoft.AspNetCore.Http.HttpContextAccessor)factory).HttpContext;
// context.Response.WriteAsync("Test");
return context;
}
}
} // End Class HttpContext
}
Now in Configure, where you added the IServiceProvider svp, save this service provider into the static variable "ServiceProvider" in the just created dummy class System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)
and set HostingEnvironment.IsHosted to true
System.Web.Hosting.HostingEnvironment.m_IsHosted = true;
this is essentially what System.Web did, just that you never saw it (I guess the variable was declared as internal instead of public).
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
ServiceProvider = svp;
System.Web.HttpContext.ServiceProvider = svp;
System.Web.Hosting.HostingEnvironment.m_IsHosted = true;
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "MyCookieMiddlewareInstance",
LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Unauthorized/"),
AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
CookieSecure = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest
, CookieHttpOnly=false
});
Like in ASP.NET Web-Forms, you'll get a NullReference when you're trying to access a HttpContext when there is none, such as it used to be in Application_Start in global.asax.
I stress again, this only works if you actually added
services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
like I wrote you should.
Welcome to the ServiceLocator pattern within the DI pattern ;)
For risks and side effects, ask your resident doctor or pharmacist - or study the sources of .NET Core at github.com/aspnet, and do some testing.
Perhaps a more maintainable method would be adding this helper class
namespace System.Web
{
public static class HttpContext
{
private static Microsoft.AspNetCore.Http.IHttpContextAccessor m_httpContextAccessor;
public static void Configure(Microsoft.AspNetCore.Http.IHttpContextAccessor httpContextAccessor)
{
m_httpContextAccessor = httpContextAccessor;
}
public static Microsoft.AspNetCore.Http.HttpContext Current
{
get
{
return m_httpContextAccessor.HttpContext;
}
}
}
}
And then calling HttpContext.Configure in Startup->Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
System.Web.HttpContext.Configure(app.ApplicationServices.
GetRequiredService<Microsoft.AspNetCore.Http.IHttpContextAccessor>()
);
There is a solution to this if you really need a static access to the current context.
In Startup.Configure(….)
app.Use(async (httpContext, next) =>
{
CallContext.LogicalSetData("CurrentContextKey", httpContext);
try
{
await next();
}
finally
{
CallContext.FreeNamedDataSlot("CurrentContextKey");
}
});
And when you need it you can get it with :
HttpContext context = CallContext.LogicalGetData("CurrentContextKey") as HttpContext;
I hope that helps. Keep in mind this workaround is when you don’t have a choice. The best practice is to use de dependency injection.

Categories

Resources