2016-04-28 69 views
2

我的一個依賴項(DbContext)是使用WebApiRequestLifestyle作用域註冊的。WebApiRequestLifestyle和BackgroundJob混淆

現在,我的後臺作業使用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); 
     } 
    } 
} 
+0

您是否使用'Hangfire.SimpleInjector' NuGet包並將'JobActivator.Current'設置爲'SimpleInjectorJobActivator'? – Steven

+0

@Steven是的,我是我很抱歉,我忘了在上面的代碼中顯示它。我剛添加它。 – Marco

回答

3

我想知道當Hangfire調用我爲後臺作業註冊的方法時,這是如何工作的。由於web api沒有涉及,DbContext會被視爲一個傳遞對象嗎?

正如design decisions所描述的那樣,Simple Injector絕不允許您在活動範圍之外解析實例。因此DbContext既不會被解析爲瞬態或單身;沒有範圍時,簡單注射器將引發異常。

每種應用類型都需要自己的範圍生活方式。 Web API需要WebApiRequestLifestyle,WCF爲WcfOperationLifestyle,MVC爲WebRequestLifestyle。對於Windows服務,您通常會使用LifetimeScopeLifestyleExecutionScopeLifestyle

如果您的Hangfire作業在Windows服務中運行,則必須使用LifetimeScopeLifestyleExecutionScopeLifestyle。這些範圍需要明確的開始。

在Web(或Web API)應用程序中的後臺線程上運行作業時,無法訪問所需的上下文,這意味着如果嘗試這樣做,Simple Injector將拋出異常。

但是,您正在使用Hangfire.SimpleInjector集成庫。該庫實現了一個名爲SimpleInjectorJobActivator的自定義JobActivator實現,此實現將在後臺線程上爲您創建一個ExecutionContextScope。在這個執行上下文範圍內,Hangfire實際上會解決你的Mailer。所以MailNotificationHandler中的Mailer構造函數參數實際上從來沒有使用; Hangfire會爲你解決這個問題。

WebApiRequestLifestyleExecutionContextScopeLifestyle是可互換的; WebApiRequestLifestyle在後臺使用執行上下文作用域,SimpleInjectorWebApiDependencyResolver實際上啓動了執行上下文作用域。所以有趣的是,你的WebApiRequestLifestyle也可以用於後臺操作(雖然它可能有點混亂)。所以你的解決方案工作正常。

然而,當在MVC中運行,這是不行的,在這種情況下,你必須創建一個Hybrid lifestyle,例如:

var container = new Container(); 

container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector:() => container.GetCurrentExecutionContextScope() != null, 
    trueLifestyle: new ExecutionContextScopeLifestyle(), 
    falseLifestyle: 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代理轉發該呼叫遲髮型:

// 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類在後臺線程啓動時由Hangfire解析。 Mailer的註冊是可選的,但是可取的,因爲它已經成爲一個根對象,並且由於它具有依賴關係,所以我們希望簡單注入器知道這種類型,以便它能夠驗證和診斷此註冊。

我希望你同意從MailNotificationHandler的角度來看,應用程序現在更清潔。

+0

嗯...我讀了混合動力的生活方式,但現在我正在使用WebApiRequestLifeStyle作爲我的默認範圍,當我在後臺作業上設置斷點時,它確實有效。我其實並沒有期待它。那有意義嗎? – Marco

+0

只是爲了澄清我不想訪問http上下文或任何東西只是使用WebApi範圍註冊的DbContext依賴項。 – Marco

+0

我添加了一小段我使用的代碼。 – Marco