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](ScopedRegistration
1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration
1 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, Func
1 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, IEnumerable
1 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](ScopedRegistration
1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration
1 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, Func
1 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, IEnumerable
1 filters) at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context) at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)