我使用的是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