2017-05-17 55 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

相关问题