WebApiRequestLifestyle 和 BackgroundJob 混淆
WebApiRequestLifestyle and BackgroundJob Confusion
我的一个依赖项 (DbContext) 是使用 WebApiRequestLifestyle 范围注册的。
现在,我的后台作业使用 IoC 并依赖于上面使用 WebApiRequestLifestyle 注册的服务。我想知道当 Hangfire 调用我为后台作业注册的方法时这是如何工作的。由于不涉及 Web api,DbContext 是否会被视为瞬态对象?
任何指导都会很棒!
这是我在启动期间出现的初始化代码:
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;
}
这是我启动后台作业的代码:
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);
}
}
最后是在后台线程上运行的代码:
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?
正如 design decisions 所描述的,Simple Injector 永远不允许您解析活动范围之外的实例。这样 DbContext 就不会被解析为 transient 或 singleton;当没有作用域时,Simple Injector 会抛出异常。
每种应用程序类型都需要其自己类型的范围内的生活方式。 Web API 需要 AsyncScopedLifestyle
(在以前的版本中 WebApiRequestLifestyle
),WCF 需要 WcfOperationLifestyle
和 MVC WebRequestLifestyle
。对于 Windows 服务,您通常会使用 AsyncScopedLifestyle
.
如果您的 Hangfire 作业 运行 在 Windows 服务中,您将必须使用 ThreadScopedLifestyle
或 AsyncScopedLifestyle
。这些范围需要显式启动。
当 运行在 Web(或 Web API)应用程序的后台线程上运行作业时,无法访问所需的上下文,这意味着 Simple Injector 将抛出异常如果您尝试这样做。
但是您正在使用 Hangfire.SimpleInjector
集成库。该库实现了一个名为 SimpleInjectorJobActivator
的自定义 JobActivator
实现,该实现将在后台线程上为您创建一个 Scope
。 Hangfire 实际上会在此执行上下文范围内解析您的 Mailer
。所以 Mailer
中的 MailNotificationHandler
构造函数参数实际上从未使用过; Hangfire 会为你解决这个类型。
WebApiRequestLifestyle
和AsyncScopedLifestyle
可以互换; WebApiRequestLifestyle
在后台使用执行上下文范围,而 SimpleInjectorWebApiDependencyResolver
实际上启动执行上下文范围。所以有趣的是你的 WebApiRequestLifestyle
也可以用于后台操作(尽管它可能有点混乱)。所以你的解决方案可以正常工作。
然而,当在 MVC 中 运行ning 时,这将不起作用,在这种情况下,您将不得不创建一个 Hybrid lifestyle,例如:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
new AsyncScopedLifestyle(),
new WebRequestLifestyle());
您可以按如下方式注册您的 DbContext:
container.Register<DbContext>(() => new DbContext(...), Lifestyle.Scoped);
如果您不介意的话,这里有一些关于您的应用程序设计的反馈。
防止让应用程序代码(例如您的 MailNotificationHandler
)直接依赖外部库(例如 Hangfire)。这直接违反了依赖倒置原则,使您的应用程序代码很难测试和维护。相反,只让您的 Composition Root(连接依赖项的地方)依赖 Hangfire。在你的情况下,解决方案非常简单,我什至会说愉快,它看起来如下:
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);
}
}
这里我们添加了一个新的IMailer
抽象,并使MailNotificationHandler
依赖于这个新的抽象;不知道任何后台处理的存在。现在靠近配置服务的部分,定义一个 IMailer
代理将调用转发到 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));
}
}
这需要进行以下注册:
container.Register<IMailer, HangfireBackgroundMailer>(Lifestyle.Singleton);
container.Register<Mailer>(Lifestyle.Transient);
这里我们将新的 HangfireBackgroundMailer
映射到 IMailer
抽象。这确保 BackgroundMailer
被注入你的 MailNotificationHandler
,而 Mailer
class 在后台线程启动时由 Hangfire 解析。 Mailer
的注册是可选的,但也是可取的,因为它已经成为一个根对象,并且由于它具有依赖性,我们希望 Simple Injector 知道这种类型,以允许它验证和诊断这种注册。
我希望你同意,从 MailNotificationHandler
的角度来看,应用程序现在更干净了。
我的一个依赖项 (DbContext) 是使用 WebApiRequestLifestyle 范围注册的。
现在,我的后台作业使用 IoC 并依赖于上面使用 WebApiRequestLifestyle 注册的服务。我想知道当 Hangfire 调用我为后台作业注册的方法时这是如何工作的。由于不涉及 Web api,DbContext 是否会被视为瞬态对象?
任何指导都会很棒!
这是我在启动期间出现的初始化代码:
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;
}
这是我启动后台作业的代码:
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);
}
}
最后是在后台线程上运行的代码:
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?
正如 design decisions 所描述的,Simple Injector 永远不允许您解析活动范围之外的实例。这样 DbContext 就不会被解析为 transient 或 singleton;当没有作用域时,Simple Injector 会抛出异常。
每种应用程序类型都需要其自己类型的范围内的生活方式。 Web API 需要 AsyncScopedLifestyle
(在以前的版本中 WebApiRequestLifestyle
),WCF 需要 WcfOperationLifestyle
和 MVC WebRequestLifestyle
。对于 Windows 服务,您通常会使用 AsyncScopedLifestyle
.
如果您的 Hangfire 作业 运行 在 Windows 服务中,您将必须使用 ThreadScopedLifestyle
或 AsyncScopedLifestyle
。这些范围需要显式启动。
当 运行在 Web(或 Web API)应用程序的后台线程上运行作业时,无法访问所需的上下文,这意味着 Simple Injector 将抛出异常如果您尝试这样做。
但是您正在使用 Hangfire.SimpleInjector
集成库。该库实现了一个名为 SimpleInjectorJobActivator
的自定义 JobActivator
实现,该实现将在后台线程上为您创建一个 Scope
。 Hangfire 实际上会在此执行上下文范围内解析您的 Mailer
。所以 Mailer
中的 MailNotificationHandler
构造函数参数实际上从未使用过; Hangfire 会为你解决这个类型。
WebApiRequestLifestyle
和AsyncScopedLifestyle
可以互换; WebApiRequestLifestyle
在后台使用执行上下文范围,而 SimpleInjectorWebApiDependencyResolver
实际上启动执行上下文范围。所以有趣的是你的 WebApiRequestLifestyle
也可以用于后台操作(尽管它可能有点混乱)。所以你的解决方案可以正常工作。
然而,当在 MVC 中 运行ning 时,这将不起作用,在这种情况下,您将不得不创建一个 Hybrid lifestyle,例如:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
new AsyncScopedLifestyle(),
new WebRequestLifestyle());
您可以按如下方式注册您的 DbContext:
container.Register<DbContext>(() => new DbContext(...), Lifestyle.Scoped);
如果您不介意的话,这里有一些关于您的应用程序设计的反馈。
防止让应用程序代码(例如您的 MailNotificationHandler
)直接依赖外部库(例如 Hangfire)。这直接违反了依赖倒置原则,使您的应用程序代码很难测试和维护。相反,只让您的 Composition Root(连接依赖项的地方)依赖 Hangfire。在你的情况下,解决方案非常简单,我什至会说愉快,它看起来如下:
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);
}
}
这里我们添加了一个新的IMailer
抽象,并使MailNotificationHandler
依赖于这个新的抽象;不知道任何后台处理的存在。现在靠近配置服务的部分,定义一个 IMailer
代理将调用转发到 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));
}
}
这需要进行以下注册:
container.Register<IMailer, HangfireBackgroundMailer>(Lifestyle.Singleton);
container.Register<Mailer>(Lifestyle.Transient);
这里我们将新的 HangfireBackgroundMailer
映射到 IMailer
抽象。这确保 BackgroundMailer
被注入你的 MailNotificationHandler
,而 Mailer
class 在后台线程启动时由 Hangfire 解析。 Mailer
的注册是可选的,但也是可取的,因为它已经成为一个根对象,并且由于它具有依赖性,我们希望 Simple Injector 知道这种类型,以允许它验证和诊断这种注册。
我希望你同意,从 MailNotificationHandler
的角度来看,应用程序现在更干净了。