2016-04-28 23 views
2

私の依存関係(DbContext)の1つは、WebApiRequestLifestyleスコープを使用して登録されています。WebApiRequestLifestyleとBackgroundJob Confusion

私のバックグラウンドジョブはIoCを使用し、WebApiRequestLifestyleを使用して上記で登録されたサービスに依存します。 Hangfireがバックグラウンドジョブのために登録したメソッドiを呼び出すときに、これがどのように動作するのか不思議です。 Web APIが関与していないので、DbContextはtransistentオブジェクトのように扱われますか?

ガイダンスは素晴らしいと思います!ここで

は、起動時に発生した、私の初期化コードである:ここで

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がバックグラウンドジョブ用に登録したメソッドiを呼び出すときの仕組み。 Web APIが関与していないので、DbContextはtransistentオブジェクトのように扱われますか?

design decisionsに記載されているように、Simple Injectorでは、アクティブなスコープ外のインスタンスを決して解決できません。したがって、DbContextは一時的またはシングルトンとして解決されません。シンプルインジェクタは、スコープがない場合に例外をスローします。

すべてのアプリケーションタイプには、独自のタイプのスコープ付きライフスタイルが必要です。 Web APIにはWebApiRequestLifestyle、WCFにはWcfOperationLifestyle、MVCにはWebRequestLifestyleが必要です。 Windowsサービスの場合は、通常、LifetimeScopeLifestyleまたはExecutionScopeLifestyleのいずれかを使用します。

WindowsサービスでHangfireジョブを実行する場合は、LifetimeScopeLifestyleまたはExecutionScopeLifestyleのいずれかを使用する必要があります。これらのスコープは明示的に開始する必要があります。

Web(またはWeb API)アプリケーションのバックグラウンドスレッドでジョブを実行すると、必要なコンテキストにアクセスすることができず、Simple Injectorが例外をスローすることになります。

ただし、Hangfire.SimpleInjector統合ライブラリを使用しています。このライブラリはJobActivatorという実装を実装しています。この実装では、SimpleInjectorJobActivatorと呼ばれ、バックグラウンドスレッドでExecutionContextScopeが作成されます。 Hangfireはこの実行コンテキストスコープのコンテキスト内で実際にMailerを解決します。したがって、MailNotificationHandlerMailerコンストラクタ引数は実際には使用されません。ハングファイアがこのタイプを解決します。

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依存を作りました。バックグラウンド処理の存在を知らない。今、あなたはあなたのサービスを構成する部品の近くに、Hangfireに通話を転送する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抽象化。これにより、MailNotificationHandlerBackgroundMailerが注入され、バックグラウンドスレッドの開始時にMailerクラスがHangfireによって解決されます。 Mailerの登録は必須ではありませんが、ルートオブジェクトになっており、依存関係があるため、Simple Injectorがこのタイプを認識してこの登録を確認して診断できるようにします。

私はあなたがMailNotificationHandlerの観点から、今アプリケーションははるかにクリーンであることに同意したいと思います。

+0

hmm ...ハイブリッドライフスタイルについて読んでいますが、今ではWebApiRequestLifeStyleをデフォルトスコープとして使用しています。バックグラウンドジョブでブレークポイントを設定すると、実際に動作します。私は実際にそれを期待していませんでした。それは理にかなっていますか? – Marco

+0

と私はHTTPコンテキストまたはWebApiスコープを使用して登録されているDbContext依存関係だけにアクセスしようとしていないことを明確にするためです。 – Marco

+0

私はちょっとしたコードを追加しました。 – Marco