Mar*_*rco 4 .net c# asp.net simple-injector hangfire
我的依赖项之一(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;
}
Run Code Online (Sandbox Code Playgroud)
这是我的启动后台作业的代码:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
最后,这里是在后台线程上运行的代码:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我想知道当Hangfire调用我为后台作业注册的方法时,该方法如何工作。由于不涉及Web api,因此DbContext是否将被视为瞬态对象?
正如设计决策所描述的那样,Simple Injector将永远不允许您解析活动范围之外的实例。这样DbContext将不会被解析为瞬态或单例;如果没有作用域,则简单注入器将引发异常。
每种应用程序类型都需要其自己的范围化生活方式。Web API需要AsyncScopedLifestyle(在以前的版本中WebApiRequestLifestyle),WCF WcfOperationLifestyle和MVC WebRequestLifestyle。对于Windows服务,通常会使用AsyncScopedLifestyle。
如果您的Hangfire作业在Windows服务中运行,则必须使用ThreadScopedLifestyle或AsyncScopedLifestyle。这些范围需要明确的开始。
在Web(或Web API)应用程序的后台线程上运行作业时,无法访问所需的上下文,这意味着Simple Injector将在尝试抛出异常时抛出异常。
但是,您正在使用Hangfire.SimpleInjector集成库。该库实现了一个自定义JobActivator实现SimpleInjectorJobActivator,该实现将Scope在后台线程上为您创建start a 。Hangfire实际上将Mailer在此执行上下文范围内解决您的问题。因此Mailer,您MailNotificationHandler实际上从未使用过构造函数参数;Hangfire将为您解决此类型。
的WebApiRequestLifestyle和AsyncScopedLifestyle是可互换的; 会WebApiRequestLifestyle在后台使用执行上下文范围,而SimpleInjectorWebApiDependencyResolver实际上会启动执行上下文范围。因此,有趣的是您WebApiRequestLifestyle也可以用于后台操作(尽管可能会造成一些混乱)。因此,您的解决方案可以正常工作。
但是,在MVC中运行时,这将不起作用,在这种情况下,您将必须创建Hybrid生活方式,例如:
var container = new Container();
container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
new AsyncScopedLifestyle(),
new WebRequestLifestyle());
Run Code Online (Sandbox Code Playgroud)
您可以按照以下方式注册DbContext:
container.Register<DbContext>(() => new DbContext(...), Lifestyle.Scoped);
Run Code Online (Sandbox Code Playgroud)
如果您不介意的话,这是有关应用程序设计的一些反馈。
防止让应用程序代码(例如your)MailNotificationHandler直接依赖于外部库(例如Hangfire)。这直接违反了依赖倒置原则,使您的应用程序代码很难测试和维护。相反,仅让您的“合成根目录”(连接依赖项的地方)依赖于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);
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们添加了一个新的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));
}
}
Run Code Online (Sandbox Code Playgroud)
这需要以下注册:
container.Register<IMailer, HangfireBackgroundMailer>(Lifestyle.Singleton);
container.Register<Mailer>(Lifestyle.Transient);
Run Code Online (Sandbox Code Playgroud)
在这里,我们将新的映射HangfireBackgroundMailer到IMailer抽象。这确保了BackgroundMailer注入到你的MailNotificationHandler,而Mailer类是由迟发型当后台线程启动解决。的注册Mailer是可选的,但建议这样做,因为它已成为根对象,并且具有依赖关系,因此我们希望Simple Injector知道这种类型,以使其能够验证和诊断这种注册。
我希望您同意从的角度来看MailNotificationHandler,该应用程序现在更加干净。