Darren Kopp

Navigating through software, line by line

Getting Autofac, NServiceBus, ASP.NET MVC and Web API to play together

Sometimes things that work seamlessly by themselves have trouble working with each other.
Autofac integrates seamlessly with ASP.NET MVC and Web API.
NServiceBus has support for Autofac as one of it's IContainer implementations.
What could go wrong?

A subtle problem arises when you start trying to do the "unit of work" approach to managing things like
Entity Frameworks DbContext. We want a single DbContext instance for each web request and for it to be
disposed at the end of each request so that we don't leak any resources.
Autofac has great support for this through their ASP.NET integration so that certain components
are scoped to have a single instance per request. This even works with ASP.NET Web API even.

NServiceBus... has a problem with this though. Since Autofac's integration starts a new lifetime scope
with a specific key, when NServiceBus receives a message and tries to get a handler from Autofac, it is unable
to fulfill the request because we are not in a web request.

The fix

So, how can we get everything to work together? Simple: throw out all the existing functionality and start from scratch.
Sounds terrible but it isn't as hard as it sounds. Now, I should mention that I got the idea from this a few months ago
while looking at the source code for the Orchard project, so credit should go to them for this brilliant implementation.

The infrastructure

There are three core interfaces we'll use to accomplish this: IWorkContext, IWorkContextScope, and IWorkContextAccessor.
IWorkContext isn't necessary, but it is a somewhat nice abstraction between us and Autofac.
IWorkContextScope is what tracks the ILifetimeScope for the request.
IWorkContextAccessor is a component that can be injected into others that allow them to find the current work context or start
a new scope. Below is the definition for IWorkContextAccessor and IWorkContextScope, and I'll put the code for the implementation at the end of this post.

NServiceBus

Now that we have our infrastructure in place, we can hook into NServiceBus via the IMessageModule interface.
We'll start a new scope in the HandleBeginMessage method and dispose of it in the HandleEndMessage method.
Now, since there can be multiple threads handling messages, but there is only one IMessageModule instance per type, we'll
need to store a separate instance of our current scope for each thread. Below is an implementation that does this.

Autofac

There's only a couple of issues that we need to tackle here. Unfortunately, ASP.NET MVC and Web API use different service locator
providers, so we have to make our implementation work with both. On the plus side, we can leverage 50% of the work Autofac has already
done since we can just override the functionality on the MVC side. The Web API side we aren't so lucky.

MVC

So for MVC we can just inherit from RequestLifetimeScopeProvider and provide the container to Autofac.
From there, Autofac takes care of disposing the lifetime scope at the end of an HTTP request for us.

Web API

There wasn't a simple way to extend the Autofac Web API integration, so I had to just copy the existing implemenation from the source.
There's two components we need to implement here: IDependencyResolver and IDependencyScope.
These are basically analogous to our IWorkContext and IWorkContextScope. I'll put implmentations at the end.

Tying it all together

The only thing left to do is wire up Autofac to make this all work. For the most part, you likely won't have many components
that need to be scoped to the work context, but we want to make this as smooth as Autofac's ASP.NET integration.

Now, anywhere where we need to register something for the unit of work like the DbContext, we would call
builder.Register(c => ...).InstancePerWorkContext();

Mission Accomplished

While you may not be using all of the same components together as I am, you may find yourself needing to use the same
infrastructure outside of a http request and finding yourself stuck. Hopefully this will help you get through it.

DefaultWorkContext

classDefaultWorkContextAccessor:IWorkContextAccessor,IDisposable{staticreadonlyobjectCONTEXT_KEY=newobject();privatereadonlyILifetimeScopeLifetime;readonlyThreadLocal<IWorkContext>ThreadContext=newThreadLocal<IWorkContext>();readonlyobjectTag;publicDefaultWorkContextAccessor(objecttag,ILifetimeScopelifetime){Tag=tag;Lifetime=lifetime;}publicIWorkContextGetContext(){varhttpContext=HttpContext.Current;if(httpContext!=null)returnResolveHttpContext(newHttpContextWrapper(httpContext));returnThreadContext.Value;}privateIWorkContextResolveHttpContext(HttpContextBasehttpContext){IWorkContextcontext=httpContext.Items[CONTEXT_KEY]asIWorkContext;if(context!=null)returncontext;// find autofac managed lifetimeILifetimeScopelifetime=httpContext.Items[typeof(ILifetimeScope)]asILifetimeScope;if(autofacManagedLifetime!=null){context=newDefaultWorkContext(lifetime);httpContext.Items[CONTEXT_KEY]=context;returncontext;}returnnull;}publicIWorkContextScopeCreateScope(){varworkLifetime=Lifetime.BeginLifetimeScope(Tag);try{varhttpContext=HttpContext.Current;if(httpContext!=null)returnnewHttpWorkContextScope(workLifetime,newHttpContextWrapper(httpContext));returnnewThreadLocalContextScope(workLifetime,ThreadContext);}catch{// if there is a problem, kill lifetime then rethrowworkLifetime.Dispose();throw;}}publicvoidDispose(){ThreadContext.Dispose();}abstractclassAbstractScope:IWorkContextScope{booldisposed=false;readonlyIWorkContextWorkContext;readonlyILifetimeScopeScope;publicAbstractScope(ILifetimeScopescope){Scope=scope;WorkContext=scope.Resolve<IWorkContext>();}publicIWorkContextContext{get{returnWorkContext;}}publicvoidDispose(){if(disposed)return;disposed=true;using(Scope)OnDispose();}protectedvirtualvoidOnDispose(){}}classHttpWorkContextScope:AbstractScope{readonlyHttpContextBaseHttpContext;publicHttpWorkContextScope(ILifetimeScopelifetime,HttpContextBasehttpContext):base(lifetime){HttpContext=httpContext;HttpContext.Items[CONTEXT_KEY]=Context;lifetime.CurrentScopeEnding+=delegate{HttpContext.Items.Remove(CONTEXT_KEY);};}}classThreadLocalContextScope:AbstractScope{privatereadonlyThreadLocal<IWorkContext>ContextStorage;publicThreadLocalContextScope(ILifetimeScopescope,ThreadLocal<IWorkContext>contextStorage):base(scope){ContextStorage=contextStorage;ContextStorage.Value=Context;}protectedoverridevoidOnDispose(){ContextStorage.Value=null;}}}