bling.github.io

This blog has relocated to bling.github.io.

Saturday, September 26, 2009

Autofac’s extremely powerful and flexible ContainerScope

I need to show some love and support for my favorite IoC tool because it’s most powerful feature needs more explaining.  It’s not that the main site doesn’t provide a good explanation, because it does, but because most people don’t really understand what the solution is solving.
The following is on Autofac’s front page:
var container = // ...
using (var inner = container.CreateInnerContainer())
{
  var controller = inner.Resolve<IController>();
  controller.Execute(); // use controller..
}
The first time I saw this I basically glanced right passed it.  Honestly I didn’t think anything of it, and initially the reason I tried out Autofac was for its very slick lambda registrations.  I didn’t realize I was in a world of surprises when I finally realized the power and flexibility of Autofac’s container scope.

If benchmarks and feature comparisons are not enough show off Autofac’s features, this blog post hopes to show how to solve “complex” problems elegantly.

Let’s start with the typical NHibernate use-case.  Create 1 singleton, create many sessions-per-request.  Here’s a solution with Ninject (not that I’m picking on Ninject, because I love its very slick contextual binding, but because most other IoC containers have a similar solution, like per-session with WCF & Windsor).

Basically, the solutions mentioned above will following an approach like this:

a) Hook into the beginning of a request, and create the ISession from the ISessionFactory.
b) Set it in the HttpContext.Current or OperationContext.Current’s dictionaries.
c) Get this property in all the dependencies that need it.
d) At the end of the request, Dispose() the ISession.

OK, pretty simple and straightforward solution, but there’s one key thing that really bugs me is that by doing this we have introduced a dependency…that is, HttpContext.Current[].  That, or you could wrap that around a class, like SessionManager, again, basically coupling to a dependency under a different name.  With Autofac, we can bypass steps b and c entirely and only worry about the beginning and end of a request.

To start off, here's the basic wiring needed:
1:   var cb = new ContainerBuilder(); 
2:   cb.Register(x => CreateSessionFactory())
       .As<ISessionFactory>()
       .SingletonScoped(); 
3:   cb.Register(x => x.Resolve<ISessionFactory>().OpenSession())
       .As<ISession>()
       .ContainerScoped(); 
4:   IContainer c = cb.Build(); 
5:   
6:   Assert.AreSame(c.Resolve<ISessionFactory>(), c.Resolve<ISessionFactory>()); 
7:   Assert.AreSame(c.Resolve<ISession>(), c.Resolve<ISession>()); 
8:   
9:   var inner1 = c.CreateInnerContainer(); 
10:  Assert.AreSame(c.Resolve<ISessionFactory>(), inner1.Resolve<ISessionFactory>()); 
11:  Assert.AreNotSame(c.Resolve<ISession>(), inner1.Resolve<ISession>());
That’s the configuration.  And that’s it!  Nothing more.  No additional SessionManager class.  No need to use HttpContext.Current to store the session.  Just pass ISession in with regular constructor/property injection.

Here’s how it works:

Line 2: ISessionFactory is created from CreateSessionFactory().  This is a singleton so there will always be one and only one instance of it within the container (and all child containers).

Line 3: This is where it’s interesting.  We’re saying “whenever I need an ISession, resolve ISessionFactory and call OpenSession() on it”.  Also, by specifying ContainerScope, we only get 1 instance per-container.

And this is where it’s sort of confusing with the terminology.  You can think of Autofac as a tree of containers.  The root container (variable c in this case), can create children containers (inner1 in this case, and inner1 could create an inner2, and so on).  So when something is Singleton scoped, that means that the root container, and any child containers (and child’s children) will only have 1 instance of a service.  With ContainerScope, each “node = container” in the tree gets 1 instance.

So back to the unit test above, in line 6 we verify that there is only 1 instance of ISessionFactory.  We resolve ISession twice as well, which shows that we get the same instance.

Line 9, we create an inner container, and here we see that ISessionFactory is the same for both the container c and inner container inner1.  However, the ISession resolved is different between the two.
Thus, by specifying ContainerScope, you can very easily group multiple services and dependencies together as one unit.  Implementing the Unit of Work pattern is insanely easy with Autofac.  Create services A, which depends on B, which depends on C, which all the previous depends on D.  Resolve A within a new inner container, and B, C, and D will always be the same instances.  Resolve A in another inner container and you will get a new set of instances.

Last but not least, Autofac will automatically call Dispose() on all resolved services once the container is disposed.  So for the above, once inner1.Dispose() is called, ISession.Dispose() is automatically called.  If you needed to, you can very easily hook into this mechanism and implement things like transactions and rollbacks.

I hope this blog post clears things up a little bit about Autofac’s ContainerScope!

3 comments:

Nick said...

I've been toying with a similar approach, but I'm uncertain about how to manage NHibernate's Transaction (Commits and Rollbacks) in this scenario. I've seen examples that make use of TransactionAttributes (in the case of MVC apps), and previously I had been managing transactions in the begin/end request events (in global.asax), but that ties every request to a transaction which isn't necessary. How did you go about handling transactions in this scenario?

bling said...

Thanks for dropping by Nick!

To answer the question, what I ended up doing was creating a set of services for each request/session, basically like how I described in my post.

I also wrote my own custom interception module (took some ideas from the one in contrib) based on Castle's DynamicProxy2.

The main difference was that my interceptors work iff methods have specific attributes, and with that I was able to selectively say which methods got transactions (among other things).

This made things really easy because I could have the transaction span multiple methods as one huge call or a simple 1 line method depending on where my transaction attributes appear in the code.

Nick said...

Heya Bailey! Think there is more than one 'Nick' lurking around your blog (or else I have a case of amnesia :))

Love the article, definitely need more info like this around the 'net.

Nick (mk II)