Hangfire + SimpleInjector (or any IoC)

We’ve started integrating Hangfire into our .NET MVC 5 Application, and it looks promising. But I’m having a difficult time switching from the Per-Web-Request mindset, to some other strange “in its own thread” Hangfire context.

We use SimpleInjector for our IoC. Everything works beautifully. Until I try to use ANY of my registered services with Hangfire. IRepository which depends on our DbContext cant be used because its per web request.

IEmailService inserts queued emails to a database (hence a dependency on IRepository) - can’t be used.

Am I missing some core concept? Must I not use most of my crafted IoC setup? Do I need to dumb-down any methods that hangfire calls to manually create DbContexts, manually send emails?

I see some IoC containers support child containers which [might] solve this problem, I do not think SimpleInjector does. Do we need to switch containers?

Note: I have installed and used the Hangfire.SimpleInjector module, created the ContainerJobActivator, etc… as expected. But the exception is usually the same (as to be expected): This class was registered as Per-Web-Request, but you are not in a web-request.

Ideally you should design the work items queued not to depend on any UI concerns such as session state, http context or IoC. IoC depends on http context. You should be able to reconstitute your objects, methods and parameters away from your Web stack. You can use IoC in a windows service, console app etc, but it won’t be the same instance of IoC as your Web stack. Your website will queue and may be process too, but it does so after the item is queued. Hangfire is fire and forget, so your per-web-request IoC will be immediately out of scope as soon as you’ve queued your item.

You’re correct that child containers might help solve this.
For SimpleInjector, what you will need to do is use the ExecutionContextScopeLifestyle (SimpleInjector.Extensions.ExecutionContextScoping), and register your DbContext with a hybrid lifestyle, using the Lifestyle.CreateHybrid method:

   Container.Register<IFoo, Foo>(Lifestyle.CreateHybrid(() => System.Web.HttpContext.Current != null,
        new WebApiRequestLifestyle(),
        new ExecutionContextScopeLifestyle())
    );

The tricky part is that now you have to actually create and start up an Execution Scope, and then end it when you are executing a job. I found the easiest way to do this is to actually leverage the Hangfire job activator, and call the following from within ActivateJob():

_container.BeginExecutionContextScope()

The final piece is to create a Hangfire job filter, and handle the OnPerformed hook, in order to dispose of your execution context scope once the job has completed (performed):

public class SimpleInjectorExecutionContextScopeEndFilter : JobFilterAttribute, IServerFilter
    {
        private readonly Container _container;
        public SimpleInjectorExecutionContextScopeEndFilter(Container container)
        {
            _container = container;
        }

        public void OnPerforming(PerformingContext filterContext)
        {
        }

        public void OnPerformed(PerformedContext filterContext)
        {
            var scope = _container.GetCurrentExecutionContextScope();
            if(scope != null)
                scope.Dispose();
        }
    }

You can then register this in your HF bootstrapper similarly to your Activator:

        GlobalConfiguration.Configuration.UseActivator(new SimpleInjectorJobActivator(container));
        GlobalJobFilters.Filters.Add(new SimpleInjectorExecutionContextScopeEndFilter(container));

When enqueueing jobs from your web controllers, just make sure you specify the registered service that should be constructed by the container from the Hangfire execution scope (e.g. don’t inject the dependency in your controller and use it in your queueing code):

        [HttpGet]
        [Route("api/foo/job")]
        public IHttpActionResult QueueFoo(string fooData)
        {
            var queueId = BackgroundJob.Enqueue<IFoo>(foo=> 
                foo.DoStuff(fooData, User.Identity.Name));    
            return Ok(queueId);
        }

I will caveat with this: If you are doing a lot of concurrent work, parallel threads, etc., this will not be a good approach when using DB Connections. You will eventually encounter odd errors saying that there are already open Db readers, or that Multiple Active Result sets aren’t enabled, etc. This is because the scope of the DB connection is the Job, not the thread, so if you open a connection and while that query/command is being executed, you parallelize other queries, ADO.NET won’t be able to handle this very well.

You will need to take a step back and look at your architecture and find what you’re trying to do in parallel, and how granular your HF job needs to be. If you do need to create parallel worker threads within a job, you may want to switch from injected DB contexts to creating atomic DB contexts within the repository methods, with a using stmt to dispose appropriately. Welcome to the world of complex web and non-web scopes with databases!

1 Like

Hi guys, since Hangfire 1.5.0-beta1 it is possible to create IoC container integrations with support for custom scopes/child containers. Let’s call this scope background job scope. This is very useful feature, and I’ve decided to integrate it into Hangfire.Core, without need for custom job filters to simplify the end-user experience.

I’ve added the JobActivatorScope class that can be used to implement custom scope, and added the JobActivator.BeginScope factory method to create a scope. BeginScope is called just before activating a job type, and JobActivatorScope.Dispose method is called just after the processing. So you can create custom BackgroundJob scope in the following ways:

  1. Inherit the class and create/dispose a custom scope as in Hangfire.Autofac.
  2. Use static JobActivatorScope.Current as in Hangfire.Ninject (please see this and this).
  3. Use static JobActivatorScope.Current.InnerScope to store custom scope objects (for example, some IoC containers store custom scope object in HttpContext.Current.Items dictionary).

I only updated Hangfire.Autofac and Hangfire.Ninject projects to support custom scopes. If you can, please, contribute to other IoC container integration projects to see this feature implemented.

Thank you @tafs7 and @odinserj for these detailed responses, this is the first I’ve seen other than “Use Hangfire.SimpleInjector”.

I will take a look at the Autofac and Ninject implementations and see if I can duplicate for SimpleInjector.

This is great news, @odinserj! I will definitely look into contributing for the Hangfire.SimpleInjector project.