简体   繁体   中英

HangFire with Castle Windsor use of dependencies in background job

I needed a way to run background jobs and found out about HangFire. I succesfully installed everything but I can't seem to get it working together with Windsor.

The problem:

When I use any of my dependencies in my background job function I get the following error in my HangFire dashboard :

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

I searched around and found out that I should use the NuGet package Castle.Windsor.Lifestyles for Hybrid lifestyles. But this does not work for me.

This is my code:

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()
        );
    }

Is there any solution for this? I would really like to run database operations in my background job.

Appreciate any help! Thx.

SOLUTION

I made a separate IoC container for HangFire only with the services I need! I also made a class BackroundJobHelper which I store all my functions I need to run in 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;
    }
}

Controller

In the controller I call my BackgroundJobHelper class with the function I want to run in HangFire.

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

The problem is that Hangfire runs it's own server (thread) independent of the housing app.

Your container is running inside what looks like an MVC application. So at registration time, the HttpContext is available and your services are getting registered with the PerRequest scope.

When you get to running in Hangfire, HttpContext is unavailable. Register any services you want to use in Hangfire with the PerThread scope. If you are sharing these components between the web application and your background threads, this may make things a little wonky. See here

You might want to segregate the components that are going to run in the background and register then PerThread as opposed to sharing PerThread components with both the Hangfire process and your web process.

It tells you here that you can't use PerRequest with Hangfire because of the issue you are seing.

I managed to do it using https://github.com/BredStik/HangFire.Windsor and http://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html Basically, you register your Jobs/Services and volia. Registering is done via you DI provider (Windsor) the usual way.

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

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

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

I basically reused existing stuff and just added Jobs on Container.

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

With windsorContainer being IWindsorContainer. This is done in Global.asax. In your Startup, you call:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM