2017-05-17 69 views
2

我使用的是Hangfire v1.6.12,Simple Injector v4.0.6,Hangfire.SimpleInjector v1.3.0和ASP.NET MVC 5項目。我想創建recurringjob,它會觸發並調用一個用戶標識符作爲輸入參數的方法。 這裏是我的配置:Hangfire RecurringJob + Simple Injector + MVC

public class BusinessLayerBootstrapper 
{ 
    public static void Bootstrap(Container container) 
    { 
     if(container == null) 
     { 
      throw new ArgumentNullException("BusinessLayerBootstrapper container"); 
     } 

     container.RegisterSingleton<IValidator>(new DataAnnotationsValidator(container)); 

     container.Register(typeof(ICommandHandler<>), AppDomain.CurrentDomain.GetAssemblies()); 
     container.Register(typeof(ICommandHandler<>), typeof(CreateCommandHandler<>)); 
     container.Register(typeof(ICommandHandler<>), typeof(ChangeCommandHandler<>)); 
     container.Register(typeof(ICommandHandler<>), typeof(DeleteCommandHandler<>)); 

     container.RegisterDecorator(typeof(ICommandHandler<>), typeof(TransactionCommandHandlerDecorator<>)); 

     container.RegisterDecorator(typeof(ICommandHandler<>), typeof(PostCommitCommandHandlerDecorator<>)); 

     container.Register<IPostCommitRegistrator>(() => container.GetInstance<PostCommitRegistrator>(), Lifestyle.Scoped); 

     container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ValidationCommandHandlerDecorator<>)); 
     container.RegisterDecorator(typeof(ICommandHandler<>), typeof(AuthorizationCommandHandlerDecorator<>)); 

     container.Register(typeof(IQueryHandler<,>), AppDomain.CurrentDomain.GetAssemblies()); 
     container.Register(typeof(IQueryHandler<,>), typeof(GetAllQueryHandler<>)); 
     container.Register(typeof(IQueryHandler<,>), typeof(GetByIdQueryHandler<>)); 
     container.Register(typeof(IQueryHandler<,>), typeof(GetByPrimaryKeyQueryHandler<>)); 

     container.RegisterDecorator(typeof(IQueryHandler<,>), typeof(ValidationQueryHandlerDecorator<,>)); 
     container.RegisterDecorator(typeof(IQueryHandler<,>), typeof(AuthorizationQueryHandlerDecorator<,>)); 

     container.Register<IScheduleService>(() => container.GetInstance<ScheduleService>(), Lifestyle.Scoped); 
    } 

public class Bootstrapper 
{ 
    public static Container Container { get; internal set; } 

    public static void Bootstrap() 
    { 
     Container = new Container(); 

     Container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
       defaultLifestyle: new WebRequestLifestyle(), 
       fallbackLifestyle: new AsyncScopedLifestyle()); 

     Business.BusinessLayerBootstrapper.Bootstrap(Container); 

     Container.Register<IPrincipal>(() => HttpContext.Current !=null ? (HttpContext.Current.User ?? Thread.CurrentPrincipal) : Thread.CurrentPrincipal); 
     Container.RegisterSingleton<ILogger>(new FileLogger()); 

     Container.Register<IUnitOfWork>(() => new UnitOfWork(ConfigurationManager.ConnectionStrings["PriceMonitorMSSQLConnection"].ProviderName, 
                  ConfigurationManager.ConnectionStrings["PriceMonitorMSSQLConnection"].ConnectionString), Lifestyle.Scoped); 

     Container.RegisterSingleton<IEmailSender>(new EmailSender()); 

     Container.RegisterMvcControllers(Assembly.GetExecutingAssembly()); 
     //container.RegisterMvcAttributeFilterProvider(); 

     DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(Container)); 

     Container.Verify(VerificationOption.VerifyAndDiagnose); 
    } 
} 

public class HangfireBootstrapper : IRegisteredObject 
{ 
    public static readonly HangfireBootstrapper Instance = new HangfireBootstrapper(); 

    private readonly object _lockObject = new object(); 
    private bool _started; 

    private BackgroundJobServer _backgroundJobServer; 

    private HangfireBootstrapper() { } 

    public void Start() 
    { 
     lock(_lockObject) 
     { 
      if (_started) return; 
      _started = true; 

      HostingEnvironment.RegisterObject(this); 

      //JobActivator.Current = new SimpleInjectorJobActivator(Bootstrapper.Container); 

      GlobalConfiguration.Configuration 
       .UseNLogLogProvider() 
       .UseSqlServerStorage(ConfigurationManager.ConnectionStrings["HangfireMSSQLConnection"].ConnectionString); 

      GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(Bootstrapper.Container)); 

      GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { LogEvents = true, Attempts = 0 }); 
      GlobalJobFilters.Filters.Add(new DisableConcurrentExecutionAttribute(15));     

      _backgroundJobServer = new BackgroundJobServer(); 
     } 
    } 

    public void Stop() 
    { 
     lock(_lockObject) 
     { 
      if (_backgroundJobServer != null) 
      { 
       _backgroundJobServer.Dispose(); 
      } 

      HostingEnvironment.UnregisterObject(this); 
     } 
    } 

    void IRegisteredObject.Stop(bool immediate) 
    { 
     this.Stop(); 
    } 

    public bool JobExists(string recurringJobId) 
    { 
     using (var connection = JobStorage.Current.GetConnection()) 
     { 
      return connection.GetRecurringJobs().Any(j => j.Id == recurringJobId); 
     } 
    } 
} 

及主要起點:

public class MvcApplication : HttpApplication 
{ 
    protected void Application_Start() 
    { 
     AreaRegistration.RegisterAllAreas(); 
     RouteConfig.RegisterRoutes(RouteTable.Routes); 
     BundleConfig.RegisterBundles(BundleTable.Bundles); 
     // SimpleInjector 
     Bootstrapper.Bootstrap(); 
     // Hangfire 
     HangfireBootstrapper.Instance.Start(); 
    } 

    protected void Application_End(object sender, EventArgs e) 
    { 
     HangfireBootstrapper.Instance.Stop(); 
    } 
} 

我打電話給我的控制器方法(我知道這是不是最好的變種,而只是用於測試):

public class AccountController : Controller 
{ 
    ICommandHandler<CreateUserCommand> CreateUser; 
    ICommandHandler<CreateCommand<Job>> CreateJob; 
    IQueryHandler<GetByPrimaryKeyQuery<User>, User> UserByPk; 
    IScheduleService scheduler; 

    public AccountController(ICommandHandler<CreateUserCommand> CreateUser, 
          ICommandHandler<CreateCommand<Job>> CreateJob, 
          IQueryHandler<GetByPrimaryKeyQuery<User>, User> UserByPk, 
          IScheduleService scheduler) 
    { 
     this.CreateUser = CreateUser; 
     this.CreateJob = CreateJob; 
     this.UserByPk = UserByPk; 
     this.scheduler = scheduler; 
    } 

    // GET: Account 
    public ActionResult Login() 
    { 
     // создаём повторяющуюся задачу, которая ссылается на метод 
     string jobId = 1 + "_RecurseMultiGrabbing"; 
     if (!HangfireBootstrapper.Instance.JobExists(jobId)) 
     { 
      RecurringJob.AddOrUpdate<ScheduleService>(jobId, scheduler => scheduler.ScheduleMultiPricesInfo(1), Cron.MinuteInterval(5)); 
      // добавляем в нашу БД 
      var cmdJob = new CreateCommand<Job>(new Job { UserId = 1, Name = jobId }); 
      CreateJob.Handle(cmdJob); 
     } 
     return View("Conf", new User()); 
    } 
} 

而我的班的方法如下:

public class ScheduleService : IScheduleService 
{ 
    IQueryHandler<ProductGrabbedInfoByUserQuery, IEnumerable<ProductGrabbedInfo>> GrabberQuery; 
    IQueryHandler<GetByPrimaryKeyQuery<User>, User> UserQuery; 
    ICommandHandler<CreateMultiPriceStatCommand> CreatePriceStats; 
    ICommandHandler<CreateCommand<Job>> CreateJob; 
    ICommandHandler<ChangeCommand<Job>> ChangeJob; 
    ILogger logger; 
    IEmailSender emailSender; 

    public ScheduleService(IQueryHandler<ProductGrabbedInfoByUserQuery, IEnumerable<ProductGrabbedInfo>> GrabberQuery, 
          IQueryHandler<GetByPrimaryKeyQuery<User>, User> UserQuery, 
          ICommandHandler<CreateMultiPriceStatCommand> CreatePriceStats, 
          ICommandHandler<CreateCommand<Job>> CreateJob, 
          ICommandHandler<ChangeCommand<Job>> ChangeJob, 
          ILogger logger, 
          IEmailSender emailSender) 
    { 
     this.GrabberQuery = GrabberQuery; 
     this.UserQuery = UserQuery; 
     this.CreatePriceStats = CreatePriceStats; 
     this.CreateJob = CreateJob; 
     this.ChangeJob = ChangeJob; 
     this.logger = logger; 
     this.emailSender = emailSender; 
    } 

    public void ScheduleMultiPricesInfo(int userId) 
    { 
     // some operations 
    } 
} 

當我的重複性作業嘗試運行方法的結果是拋出一個異常:

SimpleInjector.ActivationException:沒有登記 型ScheduleService可以發現和隱性登記不能 進行。 IUnitOfWork被註冊爲'Hybrid Web Request/Async Scoped'生活方式,但該實例在活動(混合Web請求/異步範圍)範圍的上下文 之外被請求。 ---> SimpleInjector.ActivationException:IUnitOfWork註冊爲 「混合Web請求/異步範圍」生活方式,但實例在活動(混合Web請求/異步 作用域)範圍之外請求的 。在 SimpleInjector.Scope.GetScopelessInstance [TImplementation]在 SimpleInjector.Advanced.Internal.LazyScopedRegistration 1.GetInstance(Scope scope) at lambda_method(Closure) at SimpleInjector.InstanceProducer.GetInstance() --- End of inner exception stack trace --- at SimpleInjector.InstanceProducer.GetInstance() at SimpleInjector.Container.GetInstance(Type serviceType) at Hangfire.SimpleInjector.SimpleInjectorScope.Resolve(Type type) at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_0.<PerformJobWithFilters>b__0() at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func 1延續在 Hangfire.Server.BackgroundJobPerformer(ScopedRegistration 註冊,範圍範圍))。 <> c__DisplayClass8_1.b__2() 在 Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter 濾波器,PerformingContext preContext,函數功能1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.<PerformJobWithFilters>b__2() at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable 1濾波器)在 Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext上下文) 在Hangfire.Server.Worker .PerformJob(BackgroundProcessContext背景下, IStorageConnection連接字符串的jobId)

不能明白還有什麼我需要做的。我有一個想法,我需要手動開始執行範圍,但從哪裏開始並關閉它我無法弄清楚。你能給我一些建議嗎?

修訂

我改變了我的重複性作業調用此一個:

​​

和登記本:

public class Bootstrapper 
{ 
    public static Container Container { get; internal set; } 

    public static void Bootstrap() 
    { 
     Container = new Container(); 

     Container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
       defaultLifestyle: new WebRequestLifestyle(), 
       fallbackLifestyle: new AsyncScopedLifestyle()); 

     Business.BusinessLayerBootstrapper.Bootstrap(Container); 
     Container.Register<Hangfire.JobActivator, Hangfire.SimpleInjector.SimpleInjectorJobActivator>(Lifestyle.Scoped); 

     Container.Register<IPrincipal>(() => HttpContext.Current !=null ? (HttpContext.Current.User ?? Thread.CurrentPrincipal) : Thread.CurrentPrincipal); 
     Container.RegisterSingleton<ILogger, FileLogger>(); 
     Container.RegisterSingleton<IEmailSender>(new EmailSender()); 
     // this line was moved out from BusinessLayerBootstrapper to Web part 
     Container.Register<IScheduleService, Business.Concrete.ScheduleService>(); 

     string provider = ConfigurationManager.ConnectionStrings["PriceMonitorMSSQLConnection"].ProviderName; 
     string connection = ConfigurationManager.ConnectionStrings["PriceMonitorMSSQLConnection"].ConnectionString; 
     Container.Register<IUnitOfWork>(() => new UnitOfWork(provider, connection), 
             Lifestyle.Scoped); 

     Container.RegisterMvcControllers(Assembly.GetExecutingAssembly()); 
     DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(Container)); 

     Container.Verify(VerificationOption.VerifyAndDiagnose); 
    } 
} 

這可以幫助我解決註冊問題的ScheduleService,但異常的第二部分是相同的(StackTrace也與上面提到的相同):

SimpleInjector.ActivationException:該IUnitOfWork被登記爲「混合Web請求/異步程序作用域的生活方式,但是實例是 活性(混合Web請求/異步 作用域)範圍的上下文外部請求。 在SimpleInjector.Scope.GetScopelessInstance [TImplementation](ScopedRegistration 1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration 1名登記,範圍範圍) 在SimpleInjector.Advanced.Internal.LazyScopedRegistration 1.GetInstance(Scope scope) at lambda_method(Closure) at SimpleInjector.InstanceProducer.BuildAndReplaceInstanceCreatorAndCreateFirstInstance() at SimpleInjector.InstanceProducer.GetInstance() at SimpleInjector.Container.GetInstanceForRootType(Type serviceType) at SimpleInjector.Container.GetInstance(Type serviceType) at Hangfire.SimpleInjector.SimpleInjectorScope.Resolve(Type type) at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_0.<PerformJobWithFilters>b__0() at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func 1個續) 在Hangfire.Server.BackgroundJobPerformer。 <> c__DisplayClass8_1.b__2() 在Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter濾波器,PerformingContext preContext,函數功能1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.<PerformJobWithFilters>b__2() at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable 1濾波器) 在Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext上下文) 在Hangfire.Server.Worker.PerformJob (BackgroundProcessContext背景下,IStorageConnection連接字符串的jobId)

+0

你可能已經得到了的「後提交」從[我的博客](https://cuttingedge.it/blogs/steven/pivot/entry.php?id=93),但請注意這個想法該文章的警告。正如警告中所解釋的,我通常建議不要使用這種方法。 – Steven

+0

@史蒂文你說得對,史蒂文。我喜歡你的方法,我決定在我的應用程序中選擇它。當然,我理解你的警告。但是...是否將用戶信息存儲在數據庫中使用GUID作爲標識符(整數佔用更少的空間)?如果我需要立即返回標識符而不是另一個屬性或整個對象呢? – Dmitry

+1

與INT相比,GUID佔用12個字節的額外磁盤空間。這應該不成問題。然而,在使用GUIDS時會有性能損失,但是我從未在系統中工作過,因爲這會導致無法解決的性能問題。另一方面,使用Guids有很多好處。我在返回整個對象時看不到問題。該對象只包含一個GUID ID而不是一個INT ID。 – Steven

回答

2

我創建ScopeFilter類爲Steven(SimpleInjector creator)給我的代碼示例,它看起來像提醒:

public class SimpleInjectorAsyncScopeFilterAttribute : JobFilterAttribute, IServerFilter 
{ 
    private static readonly AsyncScopedLifestyle lifestyle = new AsyncScopedLifestyle(); 

    private readonly Container _container; 

    public SimpleInjectorAsyncScopeFilterAttribute(Container container) 
    { 
     _container = container; 
    } 

    public void OnPerforming(PerformingContext filterContext) 
    { 
     AsyncScopedLifestyle.BeginScope(_container); 
    } 

    public void OnPerformed(PerformedContext filterContext) 
    { 
     var scope = lifestyle.GetCurrentScope(_container); 
     if (scope != null) 
      scope.Dispose(); 
    } 
} 

然後,所有我們需要的是在全球遲髮型配置中添加此過濾器:

GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(Bootstrapper.Container)); 
GlobalJobFilters.Filters.Add(new SimpleInjectorAsyncScopeFilterAttribute(Bootstrapper.Container)); 
+0

你能否詳細說明爲什麼默認的'SimpleInjectorJobActivator'在它的工作中失敗了?你是否更改了作業激活器的實現,以便組件不會啓動AsyncScope? –

3

的異常狀態:

的IUnitOfWork被註冊爲「混合Web請求/異步作用域的生活方式,但實例請求外活動(混合Web請求/異步範圍)範圍的上下文。

所以,換句話說,您創建了一個混合的生活方式由WebRequestLifestyleAsyncScopedLifestyle,但既沒有活躍的網絡請求,也不是異步範圍。這意味着你正在後臺線程上運行(並且堆棧跟蹤證實了這一點),而你正在從Simple Injector中解析,而你沒有明確地將操作包裝在異步範圍中。在你顯示的所有代碼中沒有任何跡象表明你確實在做這件事。

要在Hangfire創建作業之前開始和結束範圍,可以實施自定義JobActivator。例如:

using SimpleInjector; 
using SimpleInjector.Lifestyles; 

public class SimpleInjectorJobActivator : JobActivator 
{ 
    private readonly Container container; 

    public SimpleInjectorJobActivator(Container container) 
    { 
     this.container = container; 
    } 

    public override object ActivateJob(Type jobType) => this.container.GetInstance(jobType); 
    public override JobActivatorScope BeginScope(JobActivatorContext c) 
     => new JobScope(this.container); 

    private sealed class JobScope : JobActivatorScope 
    { 
     private readonly Container container; 
     private readonly Scope scope; 

     public JobScope(Container container) 
     { 
      this.container = container; 
      this.scope = AsyncScopedLifestyle.BeginScope(container); 
     } 

     public override object Resolve(Type type) => this.container.GetInstance(type); 
     public override void DisposeScope() => this.scope?.Dispose(); 
    }   
} 
+0

嗯......這是否意味着[this](https://github.com/devmondo/HangFire.SimpleInjector/blob/master/src/HangFire.SimpleInjector/SimpleInjectorJobActivator.cs)的實現不正確? – Dmitry

+0

我基於該實現的代碼示例,所以是的,它是正確的。 – Steven

+0

但是我已經在我的答案中提到了......'GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(Bootstrapper.Container));'。可能是我需要手動設置其他地方像'_container.BeginExecutionScope()'?如果是的話,那麼我需要把它放在哪裏。 – Dmitry

相關問題