我使用的是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.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。 <> 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)
你可能已经得到了的“后提交”从[我的博客](https://cuttingedge.it/blogs/steven/pivot/entry.php?id=93),但请注意这个想法该文章的警告。正如警告中所解释的,我通常建议不要使用这种方法。 – Steven
@史蒂文你说得对,史蒂文。我喜欢你的方法,我决定在我的应用程序中选择它。当然,我理解你的警告。但是...是否将用户信息存储在数据库中使用GUID作为标识符(整数占用更少的空间)?如果我需要立即返回标识符而不是另一个属性或整个对象呢? – Dmitry
与INT相比,GUID占用12个字节的额外磁盘空间。这应该不成问题。然而,在使用GUIDS时会有性能损失,但是我从未在系统中工作过,因为这会导致无法解决的性能问题。另一方面,使用Guids有很多好处。我在返回整个对象时看不到问题。该对象只包含一个GUID ID而不是一个INT ID。 – Steven