我正在使用Hangfire v1.6.12,Simple Injector v4.0.6,Hangfire.SimpleInjector v1.3.0和ASP.NET MVC 5项目。我想创建recurringjob,它将触发并调用一个用户标识符作为输入参数的方法。 这是我的配置:
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注册为“混合Web请求/异步” Scoped'的生活方式,但在上下文之外请求实例 活动(混合Web请求/异步范围)范围。 ---&GT; SimpleInjector.ActivationException:IUnitOfWork注册为 'Hybrid Web Request / Async Scoped'的生活方式,但实例是 在活动(混合Web请求/异步)的上下文之外请求 范围。范围。在 SimpleInjector.Scope.GetScopelessInstance [TImplementation](ScopedRegistration
1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration
1 注册,范围范围) 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续) Hangfire.Server.BackgroundJobPerformer&LT;&GT; c__DisplayClass8_1.b__2() 在 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过滤器)at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context) 在Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection连接,String jobId)
无法理解我还需要做什么。我有一个想法,我需要手动开始执行范围,但从哪里开始和关闭它我无法弄清楚。你能给我一些建议吗?
已更新
我改变了我的定期工作电话:
RecurringJob.AddOrUpdate<IScheduleService>(jobId, scheduler => scheduler.ScheduleMultiPricesInfo(1), Cron.MinuteInterval(5));
注册:
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注册为'Hybrid Web Request / Async Scoped'生活方式,但实例是 在活动(混合Web请求/异步)的上下文之外请求 范围。范围。 在SimpleInjector.Scope.GetScopelessInstance [TImplementation](ScopedRegistration
1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration
1注册,范围范围) 在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续) 在Hangfire.Server.BackgroundJobPerformer。&lt;&gt; c__DisplayClass8_1.b__2() 在Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter过滤器,PerformingContext preContext,Func1 continuation) at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.<PerformJobWithFilters>b__2() at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable
1个过滤器) at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context) 在Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context,IStorageConnection连接,String jobId)
答案 0 :(得分:5)
我创建了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();
}
}
然后我们需要的是在全局hangfire配置中添加此过滤器:
GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(Bootstrapper.Container));
GlobalJobFilters.Filters.Add(new SimpleInjectorAsyncScopeFilterAttribute(Bootstrapper.Container));
答案 1 :(得分:4)
例外情况:
IUnitOfWork注册为&#39; Hybrid Web Request / Async Scoped&#39;生活方式,但在活动(混合Web请求/异步范围)范围的上下文之外请求实例。
换句话说,您创建了一个由WebRequestLifestyle
和AsyncScopedLifestyle
组成的混合生活方式,但既没有活动的Web请求也没有异步范围。这意味着您在后台线程上运行(并且堆栈跟踪确认了这一点),而您正在从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();
}
}