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 服务中,您将必须使用 ThreadScopedLifestyleAsyncScopedLifestyle。这些范围需要显式启动。

当 运行在 Web(或 Web API)应用程序的后台线程上运行作业时,无法访问所需的上下文,这意味着 Simple Injector 将抛出异常如果您尝试这样做。

但是您正在使用 Hangfire.SimpleInjector 集成库。该库实现了一个名为 SimpleInjectorJobActivator 的自定义 JobActivator 实现,该实现将在后台线程上为您创建一个 Scope。 Hangfire 实际上会在此执行上下文范围内解析您的 Mailer 。所以 Mailer 中的 MailNotificationHandler 构造函数参数实际上从未使用过; Hangfire 会为你解决这个类型。

WebApiRequestLifestyleAsyncScopedLifestyle可以互换; 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 的角度来看,应用程序现在更干净了。