In my previous post, I talk about how the problem with sessions and NHibernate (NH) can get pretty tricky to deal with; specially within ASP.NET web applications.  This post shows how you can use a pretty nice feature within NH that simplifies this problem.

A Solution

NH supports a mechanism of providing a ‘current’ session via the ISessionFactory.GetCurrentSession method by using a concept called Contextual Sessions.  This is what the NH documentation says about the feature:

Most applications using NHibernate need some form of "contextual" sessions, where a given session is in effect throughout the scope of a given context. However, across applications the definition of what constitutes a context is typically different; and different contexts define different scopes to the notion of current.

Starting with version 1.2, NHibernate added the ISessionFactory.GetCurrentSession() method. The processing behind ISessionFactory.GetCurrentSession() is pluggable. An extension interface (NHibernate.Context.ICurrentSessionContext) and a new configuration parameter (hibernate.current_session_context_class) have been added to allow pluggability of the scope and context of defining current sessions.

See the API documentation for the NHibernate.Context.ICurrentSessionContext interface for a detailed discussion of its contract. It defines a single method, CurrentSession(), by which the implementation is responsible for tracking the current contextual session. Out-of-the-box, NHibernate comes with one implementation of this interface:

  • NHibernate.Context.ManagedWebSessionContext - current sessions are tracked by HttpContext. However, you are responsible to bind and unbind an ISession instance with static methods on this class, it never opens, flushes, or closes an ISession itself.

The hibernate.current_session_context_class configuration parameter defines which NHibernate.Context.ICurrentSessionContext implementation should be used. Typically, the value of this parameter would just name the implementation class to use (including the assembly name); for the out-of-the-box implementation, however, there is a corresponding short name, "managed_web".

As you can see, NH has thought about this problem and has provided a way to make the interaction with ‘current’ sessions, way easier.  With this, the code within your implementation can get can get a little bit cleaner:

   1: namespace Web {
   2:     using System;
   3:     using System.Web;
   4:     using NHibernate.Context;
   5:     using Persistence;
   6:  
   7:     public class ContextualSessionModule : IHttpModule {
   8:  
   9:         public void Init(HttpApplication context) {
  10:             context.BeginRequest += context_BeginRequest;
  11:             context.EndRequest += context_EndRequest;
  12:         }
  13:  
  14:         public void Dispose() {
  15:         }
  16:  
  17:         private static void context_BeginRequest(object sender, EventArgs e) {
  18:             var application = (HttpApplication)sender;
  19:             var context = application.Context;
  20:  
  21:             BindSession(context);
  22:         }
  23:  
  24:         private static void BindSession(HttpContext context) {
  25:             var sessionBuilder = SessionBuilderFactory.CurrentBuilder;
  26:  
  27:             // Create a new session (it's the beginning of the request)
  28:             var session = sessionBuilder.OpenSession();
  29:  
  30:             // Tell NH session context to use it
  31:             ManagedWebSessionContext.Bind(context, session);
  32:         }
  33:  
  34:         private static void context_EndRequest(object sender, EventArgs e) {
  35:             var application = (HttpApplication)sender;
  36:             var context = application.Context;
  37:  
  38:             UnbindSession(context);
  39:         }
  40:  
  41:         private static void UnbindSession(HttpContext context) {
  42:             var sessionBuilder = SessionBuilderFactory.CurrentBuilder;
  43:  
  44:             // Get the default NH session factory
  45:             var factory = sessionBuilder.GetSessionFactory();
  46:  
  47:             // Give it to NH so it can pull the right session
  48:             var session = ManagedWebSessionContext.Unbind(context, factory);
  49:  
  50:             if (session == null) return;
  51:             session.Flush();
  52:             session.Close();
  53:         }
  54:     }
  55: }

Most of ISession management churn is pushed into the Bind and Unbind methods of the ManagedWebSessionContext class.  It’s within the implementation of those methods that storage of the ISession takes place within the HttpContext.Items property.

The biggest added value for this approach is the new implementation of ISessionBuilder, :

   1: namespace Persistence {
   2:     using NHibernate;
   3:  
   4:     public class ContextualSessionBuilder : SessionBuilderBase {
   5:         private static ISession staticSession;
   6:  
   7:         public override ISession CurrentSession {
   8:             get {
   9:                 
  10:                 ISession session;
  11:  
  12:                 try {
  13:  
  14:                     // Get the default SessionFactory
  15:                     var factory = GetSessionFactory();
  16:  
  17:                     //Let NH handle the 'churn' for you
  18:                     session = factory.GetCurrentSession();
  19:  
  20:                     staticSession = null;
  21:                 } catch {
  22:                     //HACK: Here to support the calling of sessions from the HttpApplication start
  23:                     if (staticSession == null) {
  24:                         staticSession = OpenSession();
  25:                     }
  26:  
  27:                     session = staticSession;
  28:                 }
  29:  
  30:                 return session;
  31:             }
  32:         }
  33:     }
  34: }

As you can see, this implementation of ISessionBuilder does not know (or care) about the current HttpContext instance…and why should it?  The concern of SessionBuilder is to abstract all the work related to persistence and not any of the side-effects it might have to put up with.

One important thing to note here is that the only pieces we had to change were out IHttpModule and ISessionBuilder implementations since these two respectively, deal with the persistence mechanism of our application.

Now, we’re pretty close to being done with this feature, but since the next section focuses on how to configure this piece within NH and not your application, I left that piece for another blog post.

For now, Happy Codin’!