2016-04-28 4 views
2

Одна из моих зависимостей (DbContext) зарегистрирована с использованием области WebApiRequestLifestyle.WebApiRequestLifestyle и BackgroundJob Confusion

Теперь мое фоновое задание использует IoC и зависит от службы, которая была зарегистрирована выше, с использованием WebApiRequestLifestyle. Мне интересно, как это работает, когда Hangfire называет метод, который я зарегистрировал для фонового задания. Будет ли DbContext рассматриваться как перекрестный объект, поскольку веб-api не задействован?

Любые указания были бы замечательными!

Вот мой инициализации код, который происходит во время пуска:

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

Вы используете пакет NuGet 'Hangfire.SimpleInjector' и устанавливаете' JobActivator.Current' 'SimpleInjectorJobActivator'? – Steven

+0

@Steven да, я извиняюсь, я забыл показать его в коде выше. Я просто добавил его. – Marco

ответ

3

Я интересно как это работает, когда Hangfire вызывает метод, который я зарегистрировал для фонового задания. Будет ли DbContext рассматриваться как перекрестный объект, поскольку веб-api не задействован?

Как описано в design decisions, простой инжектор никогда не позволит вам разрешить экземпляр вне активной области. Так что DbContext не будет разрешен как переходный или singleton; Простой инжектор будет генерировать исключение, если нет области видимости.

Для каждого типа применения требуется свой тип охваченного образа жизни. Веб-API требует WebApiRequestLifestyle, WCF a WcfOperationLifestyle и MVC WebRequestLifestyle. Для служб Windows вы обычно используете либо LifetimeScopeLifestyle, либо ExecutionScopeLifestyle.

Если ваши задания Hangfire запускаются в службе Windows, вам придется использовать либо LifetimeScopeLifestyle, либо ExecutionScopeLifestyle. Эти области требуют явного запуска.

При выполнении заданий в фоновом потоке в веб-приложении (или веб-API) доступ к требуемому контексту отсутствует, и это означает, что Simple Injector будет выдавать исключение, если вы попытаетесь это сделать.

Вы тем не менее используете библиотеку интеграции Hangfire.SimpleInjector. Эта библиотека реализует пользовательскую реализацию JobActivator под названием SimpleInjectorJobActivator, и эта реализация создаст для вас ExecutionContextScope для фонового потока. Hangfire фактически разрешит ваш Mailer в контексте этой области контекста выполнения. Поэтому аргумент конструктора Mailer в вашем MailNotificationHandler фактически не используется; Hangfire решит этот тип для вас.

WebApiRequestLifestyle и ExecutionContextScopeLifestyle являются взаимозаменяемыми; 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. Это является прямым нарушением Принципа инверсии зависимостей и очень сложно проверить и поддерживать код приложения. Вместо этого, пусть только ваш корневой состав (место, где вы связываете ваши зависимости), зависит от 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 является необязательной, но целесообразной, так как она стала корневым объектом, и поскольку она имеет зависимости, мы хотим, чтобы Simple Injector знал об этом типе, чтобы он мог проверить и диагностировать эту регистрацию.

Надеюсь, вы согласны с тем, что с точки зрения MailNotificationHandler приложение теперь намного чище.

+0

hmm ... я читал о гибридном стиле жизни, но прямо сейчас я использую WebApiRequestLifeStyle в качестве своей области по умолчанию, и когда я устанавливаю точку останова в своей фоновой работе, она действительно работает. На самом деле я этого не ожидал. Имеет ли это смысл? – Marco

+0

и просто для того, чтобы уточнить, что я не пытаюсь получить доступ к http-контексту или чему-либо только зависимому от DbContext, который зарегистрирован с использованием области WebApi. – Marco

+0

Я добавил немного отрезанный от кода, который я использую. – Marco