我正在使用Hangfire v1.6.12、Simple Injector v4.0.6、Hangfire.SimpleInjector v1.3.0和ASP.NET MVC 5项目。我想创建一个定期触发的任务,该任务将调用一个带有用户标识符作为输入参数的方法。 这是我的配置:
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" 生命周期,但是在没有活动 (Hybrid Web Request / Async Scoped) 作用域的上下文中请求了该实例。---> SimpleInjector.ActivationException: IUnitOfWork 被注册为 "Hybrid Web Request / Async Scoped" 生命周期,但是在没有活动 (Hybrid Web Request / Async Scoped) 作用域的上下文中请求了该实例。 at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration`1 registration) at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration`1 registration, Scope scope) at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope) at lambda_method(Closure ) at SimpleInjector.InstanceProducer.GetInstance() --- 内部异常堆栈跟踪的结尾 --- 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, Func`1 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)
我不知道还需要做什么。我有一个想法,我需要手动开始执行作用域,但是我无法确定在哪里开始和关闭它。你能给我一些建议吗?
更新
我将我的循环调用更改为以下方式:
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被注册为“混合Web请求/异步作用域”生命周期,但在没有活动(混合Web请求/异步作用域)范围的上下文中请求实例。 在SimpleInjector.Scope.GetScopelessInstance[TImplementation] (ScopedRegistration < code> 1 registration)处 在SimpleInjector.Scope.GetInstance [TImplementation](ScopedRegistration 1 registration,Scope scope)处 在SimpleInjector.Advanced.Internal.LazyScopedRegistration < code> 1.GetInstance(Scope scope)处 在lambda_method(闭包)处 在SimpleInjector.InstanceProducer.BuildAndReplaceInstanceCreatorAndCreateFirstInstance()处 在SimpleInjector.InstanceProducer.GetInstance()处 在SimpleInjector.Container.GetInstanceForRootType(Type serviceType)处 在SimpleInjector.Container.GetInstance(Type serviceType)处 在Hangfire.SimpleInjector.SimpleInjectorScope.Resolve(Type type)处 在Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)处 在Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_0.<PerformJobWithFilters>b__0()处 在Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func1 continuation)处 在Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.b__2()处 在Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func
1 continuation)处 在Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass8_1.<PerformJobWithFilters>b__2()处 在Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable
1 filters)处 在Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)处 在Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId)处