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.