Skip to content

Instantly share code, notes, and snippets.

@IanYates
Created January 20, 2016 23:30
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save IanYates/29180ce09c41e42b9a7e to your computer and use it in GitHub Desktop.
Proper integration between Hangfire and Windsor - does not take advantage of new HangFire 1.5 IoC features
To configure Hangfire
//Windsor integration
//The activator
var jobActivator = new WindsorScopedJobActivator(windsorContainer);
GlobalConfiguration.Configuration.UseActivator(jobActivator);
//And something to create and dispose of a child container in the activator for each job
var windsorJobFilter = new WindsorContainerPerJobFilterAttribute(jobActivator);
GlobalJobFilters.Filters.Add(windsorJobFilter);
public class WindsorScopedJobActivator : JobActivator
{
private readonly IWindsorContainer parentContainer;
private readonly ThreadLocal<IWindsorContainer> childContainer = new ThreadLocal<IWindsorContainer>(trackAllValues:false);
public WindsorScopedJobActivator(IWindsorContainer parentContainer)
{
this.parentContainer = parentContainer;
}
public override object ActivateJob(Type jobType)
{
return childContainer.Value.Resolve(jobType);
}
private void DisposeLocalThreadContainer()
{
if (!childContainer.IsValueCreated) return;
var c = childContainer.Value;
if (c == null) return;
childContainer.Value = null;
c.Dispose();
}
public void StartJobLifetimeScope()
{
//Defensive
DisposeLocalThreadContainer();
childContainer.Value = new WindsorContainer(
//Specific name on container - avoids race conditions when calculating
//http://nguyducthuan.com/blog/2015/01/15/Race-condition-bug-of-Windsor-Castle-child-container-3-x/
Guid.NewGuid().ToString(),
new DefaultKernel(),
new DefaultComponentInstaller()
) { Parent = parentContainer };
}
public void EndJobLifetimeScope()
{
DisposeLocalThreadContainer();
}
}
/// <summary>
/// This class runs on the same thread as the job that will be performed by HangFire
/// So we can use it to create a Windsor container instance on the thread and then dispose of it
/// This lets us properly manage transient resources :)
/// </summary>
public class WindsorContainerPerJobFilterAttribute : JobFilterAttribute, IServerFilter
{
private readonly WindsorScopedJobActivator windsorPerLifetimeScopeJobActivator;
public WindsorContainerPerJobFilterAttribute(WindsorScopedJobActivator windsorPerLifetimeScopeJobActivator)
{
this.windsorPerLifetimeScopeJobActivator = windsorPerLifetimeScopeJobActivator;
}
public void OnPerforming(PerformingContext filterContext)
{
windsorPerLifetimeScopeJobActivator.StartJobLifetimeScope();
}
public void OnPerformed(PerformedContext filterContext)
{
windsorPerLifetimeScopeJobActivator.EndJobLifetimeScope();
}
}
@IanYates
Copy link
Author

Extracted from my own codebase. Hope it's of some help to others.

I have a Windsor Job Activator that works in tandem with a globally installed job filter.
The job filter sees the start of a job, which happens on a specific thread. It tells the job activator of this, which sets up a child Windsor container in a thread local. On the same thread, Hangfire then executes the job and asks the Windsor Job Activator to create its types. The job activator gets the child container from the thread local and does its thing.

When the job is finished, the job filter tells the job activator to dispose of the child container created specifically for the thread.

I got the idea from reading a few different approaches people had taken with other IOC containers and Hangfire. Note that I think Hangfire 1.5+ has some additional IOC hooks which may make some of this no longer required.

@hikalkan
Copy link

Thank you very much.

What about this implementation which uses JobActivatorScope instead of filter? It seems more natural for Hangfire.

    public class HangfireIocJobActivator : JobActivator
    {
        private readonly IIocResolver _iocResolver;

        public HangfireIocJobActivator(IIocResolver iocResolver)
        {
            if (iocResolver == null)
            {
                throw new ArgumentNullException("iocResolver");
            }

            _iocResolver = iocResolver;
        }

        public override object ActivateJob(Type jobType)
        {
            return _iocResolver.Resolve(jobType);
        }

        public override JobActivatorScope BeginScope()
        {
            return new HangfireIocJobActivatorScope(this, _iocResolver);
        }

        class HangfireIocJobActivatorScope : JobActivatorScope
        {
            private readonly JobActivator _activator;
            private readonly IIocResolver _iocResolver;

            private readonly List<object> _resolvedObjects;

            public HangfireIocJobActivatorScope(JobActivator activator, IIocResolver iocResolver)
            {
                _activator = activator;
                _iocResolver = iocResolver;
                _resolvedObjects = new List<object>();
            }

            public override object Resolve(Type type)
            {
                var instance = _activator.ActivateJob(type);
                _resolvedObjects.Add(instance);
                return instance;
            }

            public override void DisposeScope()
            {
                _resolvedObjects.ForEach(_iocResolver.Release);
            }
        }
    }

@IanYates
Copy link
Author

The job activator scope is the new thing introduced in Hangfire 1.5. The old approach still works but the newer approach is probably better. I haven't updated to 1.5 since I didn't want to reverify the correct Windsor behaviour again.

In the code you've posted I don't see any Windsor usage - is it hidden behind the IIocResolver?

The approach seems pretty sound though. Certainly it's less hacky :)

Thanks!

@hikalkan
Copy link

IIocResolver is ASP.NET Boilerplate's IOC abstraction. You can use same by sending Windsor Container directly. As I test, it's properly working.

@whudgins
Copy link

Ended up going with @hikalkan solution for Hangfire 1.5 but wanted to say thanks @IanYates for posting this. Was stuck for a few days. Below is implementation using IWindsorContainer rather than IIocResolver for anyone else who stumbles across this.

    public class TTBJobActivator : JobActivator
    {
        private readonly IWindsorContainer _container;

        public TTBJobActivator(IWindsorContainer container)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }

            _container = container;
        }

        public override object ActivateJob(Type jobType)
        {
            return _container.Resolve(jobType);
        }

        public override JobActivatorScope BeginScope()
        {
            return new HangfireIocJobActivatorScope(this, _container);
        }

        class HangfireIocJobActivatorScope : JobActivatorScope
        {
            private readonly JobActivator _activator;
            private readonly IWindsorContainer _container;

            private readonly List<object> _resolvedObjects;

            public HangfireIocJobActivatorScope(JobActivator activator, IWindsorContainer container)
            {
                _activator = activator;
                _container = container;
                _resolvedObjects = new List<object>();
            }

            public override object Resolve(Type type)
            {
                var instance = _activator.ActivateJob(type);
                _resolvedObjects.Add(instance);
                return instance;
            }

            public override void DisposeScope()
            {
                _resolvedObjects.ForEach(_container.Release);
            }
        }
    }

@skyrawrcode
Copy link

Why did you use use child container implementation for Windsor instead of a specialized Scope accessor. In the past when working with child containers they only seemed to work if the dependencies were installed inside the child container. If the dependencies were installed in the parent container even though the child container was the resolver they were still resolved and tracked by the parent container. This makes disposing the child container do little to nothing. I know child containers are a common use in with other containers but I've always had trouble using them with windsor. I was wondering how you get around these issues?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment