Tuesday, 20 September 2011

NHibernate session management and MVC

NHibernate session management is one of these things that occasionally catches me out. Usually once it's set up on a project I forget about it because it just works, but recently I came across a problem on a project which prompted me to write this post. There's a lot to say about NHibernate session management and this post is just a small contribution to the plethora of articles on the subject.

What follows here is my implementation of session management in an MVC web application (it shouldn't matter what version of MVC you're on). What I've set out to accomplish is this:
  • Define a unit of work by using NHibernate's ICurrentSessionContext
  • Enforce the unit of work by applying transaction management across controller calls.
  • Ensure that a session and transaction is available across multiple calls to child actions.
  • Make the session available for injection via my container of choice, Windsor.
In order to make any of this happen my application needs a session factory. I'm not going to go into the details of how I construct my session factory as it isn't really relevant to this post - but suffice to say that the creation of the session factory is triggered by the Application_Start() method in Global.asax - and that I store the session factory as a static property on the HttpApplication. Actually, there is one aspect of the session factory which is relevant to this post - but I'll come back to that a little later.

The Unit of Work
In web applications the obvious scope for a unit of work is a web request. Everything that happens, persistence-wise, between when a new request arrives and when the same request completes, can be treated as a unit of work. In other words, the changes to state in your application that takes place within a single web request should be atomic; the should either be persisted or rolled back at the end of the web request.

With this in mind I've set up the following methods on Global.asax:

private void OnBeginRequest(object sender, EventArgs args)
{
if(RequestMayRequirePersistence(sender as HttpApplication) && Factory != null)
{
var session = Factory.OpenSession();
session.FlushMode = FlushMode.Auto;
ManagedWebSessionContext.Bind(HttpContext.Current, session);
}
}

private void OnEndRequest(object sender, EventArgs args)
{
if (RequestMayRequirePersistence(sender as HttpApplication) && Factory != null)
{
ManagedWebSessionContext.Unbind(HttpContext.Current, Factory);
}
}
Ignore the call to RequestMayRequirePersistence - this is just a helper method to check whether the request is for a resource that may require an NHibernate session, such as a controller.

When a request arrives, I call OpenSession() on the session factory to obtain a new session. Then I bind this session to the current HttpContext by calling the Bind method on NHibernate's ManagedWebSessionContext.

Let's take a step back. Remember that I said that there's one aspect of the session factory set-up that's relevant? Well, we've already come to it. In order for this solution to work we need to tell the session factory which type of session context to use. A session context defines the scope of given session instance. In other words, if a session instance belongs to a context then the NHibernate session factory will keep serving up that same instance whenever a call is made to OpenSession() within that context. We can tell NHibernate which session context to use when setting up the session factory. Using FluentNHibernate syntax you can set up NHibernate to use the ManagedWebSessionContext by adding this to your session factory fluent config:
   .ExposeConfiguration(config =>
config.SetProperty(Environment.CurrentSessionContextClass,
typeof(ManagedWebSessionContext).AssemblyQualifiedName))
Now let's go back to handlers for BeginRequest and EndRequest. In addition to telling NHibernate which session context we want to use, we have to define when to bind and unbind the current session from that context. That's what the OnBeginRequest and OnEndRequest methods do; they define the actual scope of our NHibernate session, our unit of work.

You can read more about NHibernate session context here.

Enforcing our Unit of Work
Now we've defined a unit of work for our application. However, this unit of work is no good to us yet because we still need to set up some controls around when transactions are created, committed, and rolled back. For my application I think the following rules make sense:
  • Begin a transaction when an action method starts executing
  • If an exception occurs during the execution of an action, roll back the transaction.
  • If an exception occurs during the rendering of a view, roll back the transaction
  • If the execution of a controller and rendering of a view complete successfully, commit the transaction
  • Any calls to child actions should 'enlist' in the current transaction, and a child action should never roll back or commit a transaction.
I have implemented these rules through an ActionFilterAttribute which I've called, uhm, TransactionAttribute. It looks like this:
public class TransactionAttribute : ActionFilterAttribute
{
//Create transaction
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller as IPersistenceController;

if (controller != null && !filterContext.IsChildAction)
{
controller.CurrentSession.BeginTransaction();
}
}

//rollback on exception
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var controller = filterContext.Controller as IPersistenceController;

if (controller != null && !filterContext.IsChildAction)
{
ITransaction transaction = controller.CurrentSession.Transaction;
if (transaction.IsActive)
{
if (filterContext.Exception != null)
{
transaction.Rollback();
}
}
}
}

//commit transaction, or rollback on exception
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var controller = filterContext.Controller as IPersistenceController;
if (controller != null && !filterContext.IsChildAction)
{
ITransaction transaction = controller.CurrentSession.Transaction;
base.OnResultExecuted(filterContext);
try
{
if (transaction.IsActive)
{
if (filterContext.Exception != null)
transaction.Rollback();
else
transaction.Commit();
}
}
finally
{
transaction.Dispose();
}
}
base.OnResultExecuted(filterContext);
}
}
There are really only two things worth noting here:
  • Controllers are cast to IPersistenceController. IPersistenceController defines a single property ISession CurrentSession {get;set;} which is used to obtain the current session.
  • I check the IsChildAction property of the ActionExecutingContext to make sure that child actions don't mess with the current transaction.
You could argue that having every controller implement IPersistenceController is a bad thing and that letting the controllers know about ISession is even worse - and I'd probably tend to agree with you. If you don't like this approach you can of course abstract the access to the current session away from the controller. I've opted for this approach because of ease of implementation; Windsor - which I'll talk about in a minute - resolves all controllers for me, and therefore sets the ISession on the IPersistenceController.CurrentSession property automatically.

Making the Session Available to Windsor
The last piece of the puzzle is to make ISession available to Windsor so that it can be injected:
   container.Register(Component.For()
.UsingFactoryMethod(() => MvcApplication.Factory.GetCurrentSession())
.LifeStyle.PerWebRequest);
Note the use of a factory method, and the call to MvcApplication.Factory.GetCurrentSession(). When Windsor needs to resolve an instance of ISession it will call GetCurrentSession() on the application's session factory. The current session is governed by the session context which we set up earlier. See? So Windsor will be handed the same instance of ISession every time it asks the factory for it during the course of a single web request.

The last thing to notice is the lifestyle that's chosen for the ISession instance: PerWebRequest. If you choose anything other than this lifestyle you'll run into trouble:
  • If you set the lifestyle to Transient then Windsor will dispose of ISession if it disposes of any objects that have been injected with ISession
  • If you set the lifestyle to Singleton then Windsor will break the scope of the Unit of Work we've so carefully set up and you'll end up with a single session for the lifetime of the application.
Summary
In this post I've shown one way of managing NHibernate sessions in the context of an ASP.NET MVC application. There are many ways of doing this, but this way seems to work well for me. Comments are welcome!







5 comments:

Gaz said...

Sounds very sensible.. :-) how do/would you adapt cope when your Unit-of-Work is not a web request but maybe a console application or a Windows service taking items off a queue?

Øyvind Valland said...

Hi Gaz,

I don't have a concrete example to how you, but the same principles would apply in a console application and a windows service. You need to define what your unit of work is (where it begins and where it ends), and you need to set your current session for the scope of the unit of work.

For example, if your windows service is an NServiceBus endpoint you could define a class that implements IMessageModule. In this class' HandleBeginMessage() method you would bind a session from the session factory to he session context, and in the HandleEndMessage() method you would unbind it.

In a console application the unit of work may be a little more arbitrary (it depends on what the app does!) and therefore your unit of work implementation may require a bit more thought. There is plenty of material out there on this, so go searching! Be warned, though: there aren't any hard and fast rules, nor any set ways of doing this. People have devised many different solutions to accomplish the same thing so my advice would be to study lots of examples and then chop and change to create a solution that suits your situation.

pghtech said...

Great post. I was curious more about how you are determining if it is a resource such as a controller within the HttpApplication space. I have researched this and transvered the HttpApplication object and don't see any easy way to make that determination without doing some heavy logic to make that determination. Chance you can elaborate on how you are doing it, or is there an easy way to make that determination? Thanks.

Øyvind Valland said...

Hi pgtech - thanks!

Deciding whether or not you may need persistence isn't that hard. I'm sure you can be much more clever than I've been, but I've just looked at the resource requested - and I don't make the session available for static resources such as images:

private static readonly string[] NoPersistenceFileExtensions = new string[] { ".jpg", ".jpeg", ".gif", ".png", ".css", ".js", ".swf", ".xap", ".ico" };
private static bool RequestMayRequirePersistence(HttpApplication httpApplication)
{
if (httpApplication == null)
return false;

HttpContext context = httpApplication.Context;
if (context == null)
return false;

string fileExtension = Path.GetExtension(context.Request.PhysicalPath);
return fileExtension != null && Array.IndexOf(NoPersistenceFileExtensions, fileExtension.ToLower()) < 0;
}

for IT the said...

I have read your blog its very attractive and impressive. I like it your blog.

ASP.NET MVC Training in Chennai


ASP.NET MVC Online Training | Online LINQ Training