Hangfire RecurringJob + Simple Injector + MVC

Viewed 3548

I'm using Hangfire v1.6.12, Simple Injector v4.0.6, Hangfire.SimpleInjector v1.3.0 and ASP.NET MVC 5 project. I want to create recurringjob which will trigger and call a method with user identifier as input parameter. Here is my configuration:

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);
        }
    }
}

And main start point:

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();
    }
}

I call my method in controller (I know that it is not best variant but simply for testing):

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());
    }
}

And my class with method looks like:

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
    }
}

As a result when my recurring job tries to run method, there is an exception thrown:

SimpleInjector.ActivationException: No registration for type ScheduleService could be found and an implicit registration could not be made. The IUnitOfWork is registered as 'Hybrid Web Request / Async Scoped' lifestyle, but the instance is requested outside the context of an active (Hybrid Web Request / Async Scoped) scope. ---> SimpleInjector.ActivationException: The IUnitOfWork is registered as 'Hybrid Web Request / Async Scoped' lifestyle, but the instance is requested outside the context of an active (Hybrid Web Request / Async Scoped) scope. at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration1 registration, Scope scope) at SimpleInjector.Advanced.Internal.LazyScopedRegistration1.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, Func1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.b__2() at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.<PerformJobWithFilters>b__2() at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable1 filters) at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)

Can't understand what else I need to do. I have one idea that I need to manually begin execution scope but where to begin and close it I can't figure out. Could you give me some advices?

UPDATED

I changed my recurring job call to this one:

RecurringJob.AddOrUpdate<IScheduleService>(jobId, scheduler => scheduler.ScheduleMultiPricesInfo(1), Cron.MinuteInterval(5));

And registration to this:

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);
    }
}

This helps me to solve registration question for ScheduleService, but second part of exception is the same (StackTrace is also same as was mentioned above):

SimpleInjector.ActivationException: The IUnitOfWork is registered as 'Hybrid Web Request / Async Scoped' lifestyle, but the instance is requested outside the context of an active (Hybrid Web Request / Async Scoped) scope. at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration1 registration, Scope scope) at SimpleInjector.Advanced.Internal.LazyScopedRegistration1.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, Func1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.b__2() at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.<PerformJobWithFilters>b__2() at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable1 filters) at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)

2 Answers
Related