ASP.NET core call async init on singleton service - c#

I have a service that asynchronously reads some content from a file in a method called InitAsync
public class MyService : IService {
private readonly IDependency injectedDependency;
public MyService(IDependency injectedDependency) {
this.injectedDependency = injectedDependency;
}
public async Task InitAsync() {
// async loading from file.
}
}
Now this service is injected into my controller.
public class MyController : Controller {
private readonly IService service;
public MyController(IService service) {
this.service = service;
}
}
Now I want a singleton instance of MyService. And I want to call InitAsync in startup.
public class Startup {
public void ConfigureServices(IServiceCollection services) {
......
services.AddSingleton<IService, MyService>();
var serviceProvider = services.BuildServiceProvider();
// perform async init.
serviceProvider.GetRequiredService<IService>().InitAsync();
}
}
What is happening is at the time of startup, an instance of MyService is created and InitAsync() is called on it. Then when I called the controller class, another instance of MyService is created which is then reused for consequent calls.
What I need is to initialize only 1 instance, called InitAsync() on it in startup and have it be reused by controllers as well.

What is happening is at the time of startup, an instance of MyService is created and InitAsync() is called on it. Then when I called the controller class, another instance of MyService is created which is then reused for consequent calls.
When you call BuildServiceProvider(), you create a separate instance of IServiceProvider, which creates its own singleton instance of IService. The IServiceProvider that gets used when resolving the IService that's provided for MyController is different to the one you created yourself and so the IService itself is also different (and uninitialised).
What I need is to initialize only 1 instance, called InitAsync() on it in startup and have it be reused by controllers as well.
Rather than attempting to resolve and initialise IService inside of Startup.ConfigureServices, you can do so in Program.Main. This allows for two things:
Using the same instance of IService for initialisation and later use.
awaiting the call to InitAsync, which is currently fire-and-forget in the approach you've shown.
Here's an example of how Program.Main might look:
public static async Task Main(string[] args)
{
var webHost = CreateWebHostBuilder(args).Build();
await webHost.Services.GetRequiredService<IService>().InitAsync();
webHost.Run();
// await webHost.RunAsync();
}
This uses async Main to enable use of await, builds the IWebHost and uses its IServiceProvider to resolve and initialise IService. The code also shows how you can use await with RunAsync if you prefer, now that the method is async.

You can use simply way to do that with nuget HostInitActions
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IService, MyService>();
services.AddAsyncServiceInitialization()
.AddInitAction<IService>(async (service) =>
{
await service.InitAsync();
});
}
This nugget ensures that your initialization action will be performed asynchronously before the application starts.
Another advantage of this approach is that this initialization action can be defined from any place where services are installed into the IServiceCollection (For example, in an extension method in another project that installs internal implementations of public interfaces). This means that the ASP.NET Core project does not need to know what service and how it should be initialized, and it will still be done.

Related

Registering IHostedService on a different service provider

Is it possible to run an IHostedService on a service provider that is not the one provided by ConfigureServices?
I am working on a library that helps configure various environments and each one has its own context (the environment context is driven by service providers). I can call .AddHostedService<>(); just fine, it doesn't throw, but the service constructor never gets called and the service doesn't start.
Edit:
I can manually kick off the services like Andy suggests in the answers, but where would that happen?
All IHostedService implementations are executed by the IWebHost that holds on to the IServiceProvider which is passed in to ConfigureServices.
The internal WebHost object holds on to an internal HostedServiceExecutor that is responsible for instantiating and running IHostedService objects.
Since you are running outside of the WebHost, you are the one responsible for instantiating, starting and stopping the services.
What you would have to do is emulate what HostedServiceExecutor does: Instantiate then iterate all services that implement IHostedService and finally call StartAsync and StopAsync
For example:
var serviceCollection = new ServiceCollection();
serviceCollection.AddHostedService<TestHostedService>();
var serviceProvider = serviceCollection.BuildServiceProvider();
// this will instantiate the services
var myHostedServices = serviceProvider.GetService<IEnumerable<IHostedService>>();
// or: var myHostedServices = serviceProvider.GetServices<IHostedService>();
// start the services up
foreach (var hostedService in myHostedServices)
{
await hostedService.StartAsync(token);
}
// ...
// Shutting down: stop the services
foreach (var hostedService in myHostedServices)
{
await hostedService.StopAsync(token);
}
This is extremely simplified code. No exception handling is done. This is just to give you the gist of how you'd do it. You should really study HostedServiceExecutor's code.
As alternative to Andy's suggested solution, you can make the IHostedService into an adapter that forward the call to its own private IServiceProvider:
public class IsolatedHostedService : IHostedService
{
private IServiceProvider provider;
// Functions as Composition Root
public IsolatedHostedService()
{
var services = new ServiceCollection();
services.AddXXX();
provider = services.BuildServiceProvider(validateScopes: true);
}
public Task StartAsync(CancellationToken t)
{
provider.GetRequiredService<YourStuff>().DoSomething();
}
}

Create scope using IServiceProvider from singleton instance

Let's say I have my own class QueueListener<TService, TPayload> inherited from BackgroundService. It opens TCP connection and listens incoming messages. On each message I would like to initialize service of TService type and pass deserialized from JSON instance of TPayload to it. TService is going to be registered as Transient, so it means to be lightweight and stateless as a handler for payload have to be (in my current task). For this purpose I am going to inject IServiceProvider in constructor of my QueueListener and create a scope on each message it receives. Does it sounds like a plan or am I overengineering? I want to avoid TService is singleton as well.
Documentation says:
It's dangerous to resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
But I am not completely sure what does it means. There is no way to inject scoped service in BackgroundService because it has Singleton lifetime. Do they warn me to stop doing things like I do?
UPD #1
I explain why I suppose to create scope on each message. The idea behind that is to prevent listener to be blocked by message processing and to provide other developers possibility to create their own handlers and do some stuff on received message. Other developers can create database connections for instance while processing and I want it to be closed and released when handling is done.
Register TService as scoped and create a new scope per message. Then resolve TService from created scope. Just read Consuming a scoped service in a background task
You can write it like this:
services.AddHostedService<MyBackgroundService>();
services.AddScoped<IScopedServicePerMessage, ScopedServicePerMessage>();
...
public class MyBackgroundService : BackgroundService
{
private readonly IServiceProvider _sp;
public MyBackgroundService(IServiceProvider sp)
{
_sp = sp;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
DoWork(stoppingToken);
return Task.CompletedTask;
}
private void DoWork(CancellationToken stoppingToken)
{
while(true)
{
var msg = GetNextMessage();
using (var scope = _sp.CreateScope())
{
var servicePerMessage = scope.ServiceProvider.GetRequiredService<IScopedServicePerMessage>();
servicePerMessage.Handle(msg);
}
}
}
...
}
Regarding this:
It's dangerous to resolve a scoped service from a singleton. It may
cause the service to have incorrect state when processing subsequent requests.
It's about the case when you inject scoped service (ef core dbcontext, for instance) directly into singleton. It's not your case.
The documentation is referring to injecting a scoped service into a singleton service. Since the injection happens at the construction of the singleton object, the scoped service would be provided at that time. This will effectively increase the lifetime of the scoped service to that of a singleton service. This is dangerous because a scoped service lifetime is often chosen explicitly to ensure that the object gets disposed quickly again.
The most common example would be a database context which owns a database connection; you want to make sure that you free up this database connection as soon as possible to free up the resources. But if you injected the context into a singleton service, it would never get disposed.
That however does not mean that there is no way to consume scoped services within a singleton service. This is done by having the singleton service create a service scope from which it can then retrieve singleton services. It’s important though that this service scope is supposed to be short-lived. So take the example from ASP.NET Core itself, where a service scope is created for every request, and do something similar. For example in your case, you could do it for every incoming message if that makes sense for your application.
To create a service scope, you should inject an IServiceScopeFactory; you can then create a scope with it like this:
public async Task Process(TPayload payload)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.GetService<TService>();
await service.Process(payload);
}
}
This pattern is strictly only necessary if you need to consume scoped services. You could resolve all other services directly without creating a scope. If you can reuse the same service instance to process all payloads, you could also inject the service as a singleton (same as registering it as transient but resolving it only once). If you need a fresh instance for every payload, then consider creating a scope even if it isn’t strictly necessary.
First, transient services are not scoped services. Transient services are usually externally owned by your code and are created each time they are resolved from the container. Container does not cache transient services.
TService is going to be registered as Transient ... For this purpose I am going to inject IServiceProvider in constructor of my QueueListener and create a scope on each message it receives.
You do not need a scope for resolving transient services. Even if you create a scope, the scope still does not manage / own transient services. That, for example, ending the lifetime of the scope does not end lifetime of transient services.
You could simply use the IServiceProvider injected in QueueListener to resolve TService. And each TService resolved should be already like what you want
lightweight and stateless as a handler for payload
With regards to
Documentation says:
What the document says might not be relevant now since you are not using scoped services. But in case you want to know the reason:
It's dangerous to resolve a scoped service from a singleton.
Singleton is a special kind of scope. Singleton services are created and cached within a "root" scope of the container, which is essentially the container itself.
If you resolve scoped service from singleton, the lifetime / scope where the service instance is resolved and cached is likely to be the "root" scope. This leads to a problem where the scoped service instance being cached inside the container, and shared across multiple client requests.
This is dangerous, because scoped services are supposed to be
Scoped lifetime services (AddScoped) are created once per client request (connection).
I don't wanted my singleton class depending on the IServiceProvider directly.
So I've used a custom factory to accomplish this goal.
May this code example help others:
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IScopedBar, ScopedBar>();
services.AddSingleton<IScopedServiceFactory<IScopedBar>, ScopedServiceFactory<IScopedBar>>(
(provider) => {
var scope = provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScopedBar>();
return new ScopedServiceFactory<IScopedBar>(() => new ScopedService<IScopedBar>(scope, service));
});
services.AddSingleton<ISingletonFoo, SingletonFoo>();
}
// ...
}
public interface ISingletonFoo
{
void DoSomethingUsingScopedServices();
}
public class SingletonFoo : ISingletonFoo
{
private readonly IScopedServiceFactory<IScopedBar> _barFactory;
public SingletonFoo(IScopedServiceFactory<IScopedBar> barFactory)
{
_barFactory = barFactory;
}
public void DoSomethingUsingScopedServices()
{
using var scopedService = _barFactory.CreateService();
scopedService.Service.DoSomething();
}
}
public interface IScopedBar
{
void DoSomething();
}
public class ScopedBar : IScopedBar
{
public void DoSomething()
{
// Do something
}
}
public interface IScopedService<T> : IDisposable
{
T Service { get; }
}
public interface IScopedServiceFactory<T>
{
IScopedService<T> CreateService();
}
public class ScopedService<T> : IScopedService<T>
{
private readonly IDisposable _scope;
public ScopedService(IDisposable scope, T service)
{
_scope = scope;
Service = service;
}
public T Service { get; }
public void Dispose()
{
_scope.Dispose();
}
}
public class ScopedServiceFactory<T> : IScopedServiceFactory<T>
{
private readonly Func<IScopedService<T>> _serviceFactory;
public ScopedServiceFactory(Func<IScopedService<T>> serviceFactory)
{
_serviceFactory = serviceFactory;
}
public IScopedService<T> CreateService()
{
return _serviceFactory();
}
}
Here is my code to consume scoped service:
public interface IScopedResolver<T> where T: class
{
TResult Resolve<TResult>(Func<T, TResult> dataFactory);
Task<TResult> ResolveAsync<TResult>(Func<T, Task<TResult>> dataFactory);
}
Implement class:
public class ScopedResolver<T> : IScopeResolver<T> where T: class
{
private readonly IServiceProvider _provider;
public ScopedResolver(IServiceProvider provider)
{
_provider = provider;
}
public TResult Resolve<TResult>(Func<T, TResult> dataFactory)
{
using IServiceScope scope = _provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<T>();
return dataFactory(service);
}
public async Task<TResult> ResolveAsync<TResult>(Func<T, Task<TResult>> dataFactory)
{
using var scope = _provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<T>();
return await dataFactory(service);
}
}
Register at startup:
services.AddSingleton(typeof(IScopedResolver<>), typeof(ScopedResolver<>));
using ScopedResolve:
public class ServiceA
{
private readonly IScopedResolver<DbContext> _context;
public ServiceA(IScopedResolver<DbContext> context)
{
_context = context;
}
public async Task<List<ClassOne>> GetListAsync()
{
return await _context.ResolveAsync(async s => await s.Set<ClassOne>().ToListAsync());
}
}

Is it possible to extend IServiceProvider during runtime

TLDR: Is it possible to modify the IServiceProvider after the Startup has ran?
I am running dll's (which implement a interface of me) during run-time. Therefore there's a file listener background job, which waits till the plugin-dll is dropped. Now I want to register classes of this dll to the dependency-injection system. Therefore I added IServiceCollection as a Singleton to the DI inside ConfigureServices to use inside another method.
In therefore I created a test-project and just tried to modify the ServiceCollection in the controller, because it was easier than stripping the background job down.
services.AddSingleton<IServiceCollection>(services);
So I added IServiceCollection to my controller to check if I can add a class to the DI after the Startup class has ran.
[Route("api/v1/test")]
public class TestController : Microsoft.AspNetCore.Mvc.Controller
{
private readonly IServiceCollection _services;
public TestController(IServiceCollection services)
{
_services = services;
var myInterface = HttpContext.RequestServices.GetService<IMyInterface>();
if (myInterface == null)
{
//check if dll exist and load it
//....
var implementation = new ForeignClassFromExternalDll();
_services.AddSingleton<IMyInterface>(implementation);
}
}
[HttpGet]
public IActionResult Test()
{
var myInterface = HttpContext.RequestServices.GetService<IMyInterface>();
return Json(myInterface.DoSomething());
}
}
public interface IMyInterface { /* ... */ }
public class ForeignClassFromExternalDll : IMyInterface { /* ... */ }
The Service was successfully added to the IServiceCollection, but the change is not persisted yet to HttpContext.RequestServices even after multiple calls the service count increases each time but I don't get the reference by the IServiceProvider.
Now my question is: Is that possible to achieve and yes how. Or should I rather not do that?
Is it possible to modify the IServiceProvider after the Startup has ran?
Short answer: No.
Once IServiceCollection.BuildServiceProvider() has been invoked, any changes to the collection has no effect on the built provider.
Use a factory delegate to defer the loading of the external implementation but this has to be done at start up like the rest of registration.
services.AddSingleton<IMyInterface>(_ => {
//check if dll exist and load it
//....
var implementation = new ForeignClassFromExternalDll();
return implementation;
});
You can now explicitly inject your interface into the controller constructor
private readonly IMyInterface myInterface;
public MyController(IMyInterface myInterface) {
this.myInterface = myInterface;
}
[HttpGet]
public IActionResult MyAction() {
return Json(myInterface.DoSomething());
}
and the load dll logic will be invoked when that interface is being resolved as the controller is resolved.

ASP.NET Core initialize singleton after configuring DI

So let's say I have a singleton class instance that I register in the DI like this:
services.AddSingleton<IFoo, Foo>();
And let's say the Foo class has a number of other dependencies (mostly repository classes that allow it to load data).
With my current understanding, the Foo instance is not created until it's first used (asked). Is there a way to initialize this class other than the constructor? Like right after ConfigureServices() completes? Or should the initialization code (loading data from db) be done in Foo's constructor?
(It would be nice if this class could load its data before the first use to speed up first time access)
Do it yourself during startup.
var foo = new Foo();
services.AddSingleton<IFoo>(foo);
Or "warm it up"
public void Configure(IApplicationBuilder app)
{
app.ApplicationServices.GetService<IFoo>();
}
or alternatively
public void Configure(IApplicationBuilder app, IFoo foo)
{
...
}
But this feels just dirty and is more a problem with your design, if you do something that you shouldn't in the constructor. Class instantiation has to be fast and if you do long-running operations within it, you break against a bunch of best practices and need to refactor your code base rather than looking for ways to hack around it
I got the same problem and I find Andrew Lock blog:
https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-3/
He explains how to do this with asp .net core 3, but he also refers to his pages on how to to this with previous version.
Lately I've been creating it as an IHostedService if it needs initialization, because to me it seems more logical to let the initialization be handled by the service itself rather than outside of it.
You can even use a BackgroundService instead of IHostedService as it's pretty similar and it only needs the implementation of ExecuteAsync
Here's the documentation for them
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services
An example of how to add the service so you can inject it directly:
services
.AddHostedService<MyService>()
.AddSingleton<MyService>(x => x
.GetServices<IHostedService>()
.OfType<MyService>()
.First());
Example of a simple service:
public class MyService : IHostedService
{
// This function will be called automatically when the host `starts`
public async Task StartAsync(CancellationToken cancellationToken)
{
// Do initialization logic
}
// This function will be called automatically when the host `stops`
public Task StopAsync(CancellationToken cancellationToken)
{
// Do cleanup if needed
return Task.CompletedTask;
}
}
Some extension methods I created later on because i needed to use the same pattern again
public static class HostedServiceExtensions
{
public static IServiceCollection AddHostedServiceAsService<T>(this IServiceCollection services) where T : class, IHostedService
=> services.AddHostedService<T>().AddSingleton(x => x.GetServices<IHostedService>().OfType<T>().First());
public static IServiceCollection AddHostedServiceAsService<T>(this IServiceCollection services, Func<IServiceProvider, T> factory) where T : class, IHostedService
=> services.AddHostedService(factory).AddSingleton(x => x.GetServices<IHostedService>().OfType<T>().First());
}
Used like
services.AddHostedServiceAsService<MyService>();
// Or like this if you need a factory
services.AddHostedServiceAsService<MyService>(x => new MyService());
Adding detail to Jérôme FLAMEN's answer, as it provided the key I required to calling an async Initialization task to a singleton:
Create a class that implements IHostedService:
public class PostStartup : IHostedService
{
private readonly YourSingleton yourSingleton;
public PostStartup(YourSingleton _yourSingleton)
{
yourSingleton = _yourSingleton;
}
// you may wish to make use of the cancellationToken
public async Task StartAsync(CancellationToken cancellationToken)
{
await yourSingleton.Initialize();
}
// implement as you see fit
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
Then, in your ConfigureServices, add a HostedService reference:
services.AddHostedService<PostStartup>();
From link.
I made some manager and I need to subscribe to events of the other services.
I didn't like doing this in the
webBuilder.Configure (applicationBuilder => ...
I think it should be in the section
webBuilder.ConfigureServices ((context, services) => ...
So, here is my answer (test on net.core 3):
public static IHostBuilder CreateHostBuilder (string [] args) =>
Host.CreateDefaultBuilder (args)
.ConfigureWebHostDefaults (webBuilder =>
{
...
services.AddSingleton<ISomeSingletonService,SomeSingletonService>();
var buildServiceProvider = services.BuildServiceProvider();
var someSingletonService = buildServiceProvider.GetRequiredService <ISomeSingletonService>();
...
});

WebApiRequestLifestyle and BackgroundJob Confusion

One of my dependencies (DbContext) is registered using the WebApiRequestLifestyle scope.
Now, my background job uses IoC and depends on the service that was registered above using the WebApiRequestLifestyle. I'm wondering how this works when Hangfire calls the method i registered for the background job. Will the DbContext be treated like a transistent object since the web api is not involved?
Any guidance would be great!
Here is my initialize code that occurs during start up:
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
var container = SimpleInjectorWebApiInitializer.Initialize(httpConfig);
var config = (IConfigurationProvider)httpConfig.DependencyResolver
.GetService(typeof(IConfigurationProvider));
ConfigureJwt(app, config);
ConfigureWebApi(app, httpConfig, config);
ConfigureHangfire(app, container);
}
private void ConfigureHangfire(IAppBuilder app, Container container)
{
Hangfire.GlobalConfiguration.Configuration
.UseSqlServerStorage("Hangfire");
Hangfire.GlobalConfiguration.Configuration
.UseActivator(new SimpleInjectorJobActivator(container));
app.UseHangfireDashboard();
app.UseHangfireServer();
}
public static Container Initialize(HttpConfiguration config)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
InitializeContainer(container);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.RegisterWebApiControllers(config);
container.RegisterMvcIntegratedFilterProvider();
container.Register<Mailer>(Lifestyle.Scoped);
container.Register<PortalContext>(Lifestyle.Scoped);
container.RegisterSingleton<TemplateProvider, TemplateProvider>();
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
config.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
return container;
}
Here is my code that kicks off the background job:
public class MailNotificationHandler : IAsyncNotificationHandler<FeedbackCreated>
{
private readonly Mailer mailer;
public MailNotificationHandler(Mailer mailer)
{
this.mailer = mailer;
}
public Task Handle(FeedbackCreated notification)
{
BackgroundJob.Enqueue<Mailer>(x => x.SendFeedbackToSender(notification.FeedbackId));
BackgroundJob.Enqueue<Mailer>(x => x.SendFeedbackToManagement(notification.FeedbackId));
return Task.FromResult(0);
}
}
Finally here is the code that runs on the background thread:
public class Mailer
{
private readonly PortalContext dbContext;
private readonly TemplateProvider templateProvider;
public Mailer(PortalContext dbContext, TemplateProvider templateProvider)
{
this.dbContext = dbContext;
this.templateProvider = templateProvider;
}
public void SendFeedbackToSender(int feedbackId)
{
Feedback feedback = dbContext.Feedbacks.Find(feedbackId);
Send(TemplateType.FeedbackSender, new { Name = feedback.CreateUserId });
}
public void SendFeedbackToManagement(int feedbackId)
{
Feedback feedback = dbContext.Feedbacks.Find(feedbackId);
Send(TemplateType.FeedbackManagement, new { Name = feedback.CreateUserId });
}
public void Send(TemplateType templateType, object model)
{
MailMessage msg = templateProvider.Get(templateType, model).ToMailMessage();
using (var client = new SmtpClient())
{
client.Send(msg);
}
}
}
I'm wondering how this works when Hangfire calls the method i registered for the background job. Will the DbContext be treated like a transistent object since the web api is not involved?
As the design decisions describe, Simple Injector will never allow you to resolve an instance outside an active scope. So that DbContext will neither be resolved as transient or singleton; Simple Injector will throw an exception when there's no scope.
Every application type requires its own type of scoped lifestyle. Web API requires the AsyncScopedLifestyle (in previous versions WebApiRequestLifestyle), WCF an WcfOperationLifestyle and MVC the WebRequestLifestyle. For Windows Services you will typically use an AsyncScopedLifestyle.
If your Hangfire jobs run in a Windows Service, you will have to use either a ThreadScopedLifestyle or the AsyncScopedLifestyle. Those scopes require explicit starting.
When running the jobs on a background thread in a web (or Web API) application, there is no access to the required context and this means that Simple Injector will throw an exception if you try to do so.
You however are using the Hangfire.SimpleInjector integration library. This library implements a custom JobActivator implementation called SimpleInjectorJobActivator and this implementation will create start a Scope for you on the background thread. Hangfire will actually resolve your Mailer within the context of this execution context scope. So the Mailer constructor argument in your MailNotificationHandler is actually never used; Hangfire will resolve this type for you.
The WebApiRequestLifestyle and AsyncScopedLifestyle are interchangeable; the WebApiRequestLifestyle uses an execution context scope in the background and the SimpleInjectorWebApiDependencyResolver actually starts an execution context scope. So the funny thing is that your WebApiRequestLifestyle can be used for background operations as well (although it can be a bit confusing). So your solution works and works correctly.
When running in MVC, however, this will not work, and in that case you would have to create a Hybrid lifestyle, for instance:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
new AsyncScopedLifestyle(),
new WebRequestLifestyle());
You can register your DbContext as follows:
container.Register<DbContext>(() => new DbContext(...), Lifestyle.Scoped);
Here's some feedback on your application's design, if you don't mind.
Prevent letting application code, such as your MailNotificationHandler, from taking a direct dependency on an external library such as Hangfire. This is a direct violation of the Dependency Inversion Principle and makes your application code very hard to test and maintain. Instead, let solely your Composition Root (the place where you wire your dependencies) take a dependency on Hangfire. In your case, the solution is really straightforward and I would even say pleasant, and it would look as follows:
public interface IMailer
{
void SendFeedbackToSender(int feedbackId);
void SendFeedbackToManagement(int feedbackId);
}
public class MailNotificationHandler : IAsyncNotificationHandler<FeedbackCreated>
{
private readonly IMailer mailer;
public MailNotificationHandler(IMailer mailer)
{
this.mailer = mailer;
}
public Task Handle(FeedbackCreated notification)
{
this.mailer.SendFeedbackToSender(notification.FeedbackId));
this.mailer.SendFeedbackToManagement(notification.FeedbackId));
return Task.FromResult(0);
}
}
Here we added a new IMailer abstraction and made the MailNotificationHandler dependent on this new abstraction; unaware of the existence of any background processing. Now close to the part where you configure your services, define an IMailer proxy that forwards the calls to Hangfire:
// Part of your composition root
private sealed class HangfireBackgroundMailer : IMailer
{
public void SendFeedbackToSender(int feedbackId) {
BackgroundJob.Enqueue<Mailer>(m => m.SendFeedbackToSender(feedbackId));
}
public void SendFeedbackToManagement(int feedbackId) {
BackgroundJob.Enqueue<Mailer>(m => m.SendFeedbackToManagement(feedbackId));
}
}
This requires the following registrations:
container.Register<IMailer, HangfireBackgroundMailer>(Lifestyle.Singleton);
container.Register<Mailer>(Lifestyle.Transient);
Here we map the new HangfireBackgroundMailer to the IMailer abstraction. This ensures that the BackgroundMailer is injected into your MailNotificationHandler, while the Mailer class is resolved by Hangfire when the background thread is started. The registration of the Mailer is optional, but advisable, since it has become a root object, and since it has dependencies, we want Simple Injector to be aware of this type to allow it to verify and diagnose this registration.
I hope you agree that from perspective of the MailNotificationHandler, the application is much cleaner now.

Categories

Resources