HangFire with Castle Windsor 在后台作业中使用依赖项

HangFire with Castle Windsor use of dependencies in background job

我需要一种方法来 运行 后台作业并发现了 HangFire。我成功安装了所有东西,但我似乎无法让它与 Windsor 一起工作。

问题:

当我在后台作业函数中使用任何依赖项时,我的 HangFire 仪表板中出现以下错误:

System.InvalidOperationException: HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net

我四处搜索,发现我应该使用 NuGet 包 Castle.Windsor.Lifestyles 来实现混合生活方式。但这对我不起作用。

这是我的代码:

Global.asax:

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        // Set up IoC Container
        var container = new WindsorContainer(Server.MapPath("~/Configuration/IoC/windsor.config"));

        // Set up HangFire with IoC
        JobActivator.Current = new WindsorJobActivator(container.Kernel);
    }

Startup.cs:

    public void Configuration(IAppBuilder app)
    {    
        GlobalConfiguration.Configuration.UseSqlServerStorage("ILVO");

        app.UseHangfireServer();
        app.UseHangfireDashboard();
    }

ServiceInstaller.cs:

public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register
        (
            Component.For<ApplicationContextBuilder>().ImplementedBy<ApplicationContextBuilder>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IApplicationContextProvider>().ImplementedBy<ApplicationContextProvider>().LifeStyle.HybridPerWebRequestTransient(),

            Component.For<ICacheService>().ImplementedBy<CacheService>().LifestyleSingleton(),
            Component.For<ISessionProvider>().ImplementedBy<SessionProvider>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IRepository>().ImplementedBy<Repository>().LifeStyle.HybridPerWebRequestTransient(),

            Component.For<IEmployeeService>().ImplementedBy<EmployeeService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IGeneralService>().ImplementedBy<GeneralService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<ITaskService>().ImplementedBy<TaskService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<ISuggestionService>().ImplementedBy<SuggestionService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IAnnouncementService>().ImplementedBy<AnnouncementService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IUploadService>().ImplementedBy<UploadService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<ITaskTrackingService>().ImplementedBy<TaskTrackingService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IRequestVpnService>().ImplementedBy<RequestVpnService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IEmailService>().ImplementedBy<EmailService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IEmployeePlannerService>().ImplementedBy<EmployeePlannerService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<ISalaryToolService>().ImplementedBy<SalaryToolService>().LifeStyle.HybridPerWebRequestTransient(),
            Component.For<IAccessRightService>().ImplementedBy<AccessRightService>().LifeStyle.HybridPerWebRequestTransient()
        );
    }

有什么解决办法吗?我真的很想 运行 在我的后台作业中进行数据库操作。

感谢任何帮助!谢谢

解决方案

我只为 HangFire 制作了一个单独的 IoC 容器,其中包含我需要的服务!我还制作了一个 class BackroundJobHelper,它存储了我需要的所有功能 运行 在 HangFire 中。

Global.asax

private WindsorContainer _hangFireContainer;

        // Set up IoC Container for HangFire
        _hangFireContainer = new WindsorContainer();
        _hangFireContainer.Register(
            Component.For<BackgroundJobHelper>(),
            Component.For<ICacheService>().ImplementedBy<CacheService>().LifestylePerThread(),
            Component.For<ISessionProvider>().ImplementedBy<SessionProvider>().LifestylePerThread(),
            Component.For<IRepository>().ImplementedBy<Repository>().LifestylePerThread(),
            Component.For<IEmployeePlannerService>().ImplementedBy<EmployeePlannerService>().LifestylePerThread(),
            Component.For<ISalaryToolService>().ImplementedBy<SalaryToolService>().LifestylePerThread()
        );

        JobActivator.Current = new WindsorJobActivator(_hangFireContainer.Kernel);

BackgroundJobHelper.cs

public class BackgroundJobHelper
{
    private readonly IEmployeePlannerService _employeePlannerService;
    private readonly ISalaryToolService _salaryToolService;

    public BackgroundJobHelper()
    {

    }

    public BackgroundJobHelper(IEmployeePlannerService employeePlannerService, ISalaryToolService salaryToolService)
    {
        _employeePlannerService = employeePlannerService;
        _salaryToolService = salaryToolService;
    }
}

控制器

在控制器中,我调用我的 BackgroundJobHelper class 和我想在 HangFire 中 运行 的功能。

BackgroundJob.Enqueue(() => _backgroundJobHelper.Function());

问题是 Hangfire 运行 是独立于住房应用程序的自己的服务器(线程)。

您的容器 运行 正在看起来像 MVC 应用程序的内部。因此在注册时,HttpContext 可用并且您的服务正在注册到 PerRequest 范围。

当您在 Hangfire 中到达 运行ning 时,HttpContext 不可用。使用 PerThread 范围注册任何你想在 Hangfire 中使用的服务。如果您在 Web 应用程序和后台线程之间共享这些组件,这可能会使事情变得有点不稳定。参见 here

您可能希望在后台隔离将要 运行 的组件,然后注册 PerThread,而不是与 Hangfire 进程和您的 Web 进程共享 PerThread 组件。

它告诉您here由于您遇到的问题,您不能将 PerRequest 与 Hangfire 一起使用。

我设法做到了 https://github.com/BredStik/HangFire.Windsorhttp://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html 基本上,您注册 Jobs/Services 和 volia。 注册是通过您的 DI 提供商 (Windsor) 以通常的方式完成的。

protected void Application_Start()
{
    _container = new WindsorContainer();            

    /* Register types */
    /* _container.Register(Component.For<ISomeInterface>().ImplementedBy<SomeImplementation>()); */

JobActivator.Current = new WindsorJobActivator(_container.Kernel);
}

我基本上重用了现有的东西,只是在容器上添加了作业。

windsorContainer.Register(Component.For<SomeJob>().ImplementedBy<SomeJob>());

windsorContainer 为 IWindsorContainer。这是在 Global.asax 中完成的。 在您的 Startup 中,您调用:

RecurringJob.AddOrUpdate<SomeJob>(x => x.Run(null), Properties.Settings.Default.SomeJobCron);