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.