Dec 5 2011

Yet Another Approach to NHibernate Session-Per-Method-Call Using StructureMap

Category: Matt @ 16:29

There are several documented approaches you can follow to implement the session-per-method-call pattern with NHibernate and StructureMap.  The majority of these approaches fail to leverage the full capabilities of StrurctureMap and are therefore more complex than they need to be.  In this short post, I’ll show you how you can implement a simpler solution by utilizing StructureMap’s nested containers.

Session-Per-Method-Call

Most web developers, whether they are using ASP.NET MVC or ASP.NET WebForms, are familiar with the session-per-request pattern.  This pattern creates an NHibernate session at the beginning of a request from a client, then flushes and disposes it once the request has ended. 

Because of the many advantages this pattern provides, it’s desirable to implement something similar for WCF applications.  Unfortunately, WCF does not expose the same set of extensibility points as an ASP.NET application.  The notion of a “request” is also somewhat different in a WCF application due to the various types of channels and behaviors WCF supports.  Instead of the session-per-request pattern, you can opt to implement the session-per-method-call pattern.  Conceptually this is very similar to session-per-request, but the implementation looks quite different.  Jimmy Bogard described one approach way back in 2008.  In it, he used a combination of a custom behavior and a mix of attributes to enable StructureMap to do session-per-method-call.  Here’s the relevant code from his article:

public class SessionPerCallInstanceProvider : IInstanceProvider
{
    private readonly Type _serviceType;
    private ISessionBuilder _sessionBuilder;
    public SessionPerCallInstanceProvider(Type serviceType)
    {
        _serviceType = serviceType;
    }
    public object GetInstance(InstanceContext instanceContext)
    {
        return GetInstance(instanceContext, null);
    }
    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        _sessionBuilder = ObjectFactory.GetInstance<ISessionBuilder>();
        return ObjectFactory
                .With(_sessionBuilder)
                .GetInstance(_serviceType);
    }
    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        if (_sessionBuilder != null)
        {
            _sessionBuilder.CloseSession();
            _sessionBuilder = null;
        }
    }
}

public class SessionPerCallServiceBehavior : Attribute, IServiceBehavior
{
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher cd = cdb as ChannelDispatcher;
            if (cd != null)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.InstanceProvider =
                        new SessionPerCallInstanceProvider(serviceDescription.ServiceType);
                }
            }
        }
    }
    public void AddBindingParameters(ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
    }
    public void Validate(ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
    }
}

[SessionPerCallServiceBehavior]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class ComboService : IComboService
{
    private readonly IProductRepository _productRepository;
    private readonly ICategoryRepository _categoryRepository;
    public ComboService(IProductRepository productRepository,
                        ICategoryRepository categoryRepository)
    {
        _productRepository = productRepository;
        _categoryRepository = categoryRepository;
    }
}

Jimmy’s approach was further refined by Frank in his post on NHibernate and WCF.  Frank implemented a custom WCF extension and NHibernate’s ICurrentSessionContext to track a single ISession instance for the duration of a method call.  Here’s a snippet showing his implementation:

class ServiceInstanceProvider<CONTRACT> : IInstanceProvider {
  ...
  public object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
  {
    var nhSessMgrExtension = instanceContext.Extensions.Find<NHibernateContextManager>();
    if (nhSessMgrExtension == null)
      instanceContext.Extensions.Add(new NHibernateContextManager());
    return kernel.Get<CONTRACT>(); //Ninject Kernel in this case, but irrelevant for this post
  }
  ...
  public void ReleaseInstance(System.ServiceModel.InstanceContext instanceContext, object instance)
  {
    var nhSessMgrExtension = instanceContext.Extensions.Find<NHibernateContextManager>();
    if (nhSessMgrExtension != null)
      instanceContext.Extensions.Remove(nhSessMgrExtension);
  }
}

class NHibernateContextManager : IExtension<InstanceContext>
{
  public ISession Session { get; set; }

  public void Attach(InstanceContext owner)
  {
    //We have been attached to the Current operation context from the 
    // ServiceInstanceProvider
  }

  public void Detach(InstanceContext owner)
  {
    if (Session != null)
    {
      Session.Flush();
      Session.Close();
    }
  }
}

public class NHWCFSessionContext : ICurrentSessionContext  
{  
  public NHWCFSessionContext(ISessionFactory factory) : base()  
  {  
    this.factory = factory;  
  }  
          
  public NHibernate.ISession CurrentSession()  
  {  
    // Get the WCF InstanceContext:  
    var contextManager = OperationContext.Current  
      .InstanceContext.Extensions.Find<NHibernateContextManager>();  
    if (contextManager == null)  
    {  
      throw new InvalidOperationException(  
@"There is no context manager available.   
Check whether the NHibernateContextManager is added as InstanceContext extension.   
Also, this Session Provider only makes sense in a WCF context.");  
    }  
  
    if (contextManager.Session == null)  
      contextManager.Session = factory.OpenSession();  
    return contextManager.Session;  
  }  
}  

While both of these approaches work, neither “felt” right to me.  Both required a fair bit of code, and neither took advantage of newer features that have since been added to StructureMap.  I took a different approach.

Session-Per-Method-Call With StructureMap Nested Containers

One of the new features introduced in StructureMap 2.6.1 was nested containers.  Unlike a normal container which does not track instances it creates, a nested container tracks all transient instances (ie: non-singleton, non-thread-local instances) and will dispose of them for you whenever you dispose the nested container.  Since ISession implements the IDisposable interface, an ISession created through a nested container will be disposed automatically when the container is disposed.  Last year, Jeremy Miller showed an example of how Dovetail utilized nested containers to implement session-per-request in a FubuMVC application.  Here’s a brief snippet from his post:

...
   using (IContainer nestedContainer = container.GetNestedContainer())
   using (var boundary = nestedContainer.GetInstance<ITransactionBoundary>())
   {
       boundary.Start();
       action(nestedContainer);
       boundary.Commit();
   }
...

I liked the simplicity of this approach and decided to adapt it for use with WCF.  I wanted to push all NHibernate and IoC concerns into the application configuration so that each WCF service would be ignorant of such things.  I also wanted to write as little code as possible, which meant I would rely heavily on StructureMap and its nested container feature.  To avoid coupling my services to StructureMap, I created a custom service behavior and associated it to my services via the application’s web.config file:

public class StructureMapServiceBehavior : BehaviorExtensionElement, IServiceBehavior
{
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
        {
            var cd = cdb as ChannelDispatcher;
            if (cd != null)
            {
                foreach (EndpointDispatcher ed in cd.Endpoints)
                {
                    ed.DispatchRuntime.InstanceProvider = new StructureMapInstanceProvider(desc.ServiceType);
                }
            }
        }
    }

    protected override object CreateBehavior()
    {
        Console.WriteLine("Bootstrapping IoC...");

        IoCBootstrapper.Bootstrap();

        return this;
    }

    public override Type BehaviorType
    {
        get { return GetType(); }
    }
}

The behavior loops through all the configured services and wires them to another class I created (based on the work of Scott Griffin), StructureMapInstanceProvider:

public class StructureMapInstanceProvider : IInstanceProvider
{
    private readonly Type _serviceType;
    private readonly IContainer _container;

    public StructureMapInstanceProvider(Type serviceType) : this(serviceType, ObjectFactory.Container)
    {
    }

    internal StructureMapInstanceProvider(Type serviceType, IContainer container)
    {
        _serviceType = serviceType;
        _container = container;
    }

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        var containerContext = instanceContext.Extensions.Find<StructureMapChildContainerContext>();

        if (containerContext == null)
        {
            containerContext = new StructureMapChildContainerContext(_container.GetNestedContainer());
            instanceContext.Extensions.Add(containerContext);
        }

        return containerContext.Container.GetInstance(_serviceType);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return this.GetInstance(instanceContext, null);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        var containerContext = instanceContext.Extensions.Find<StructureMapChildContainerContext>();

        if (containerContext != null)
        {
            instanceContext.Extensions.Remove(containerContext);
        }
    }
}

This class is doing a couple of things.  Its primary responsibility is to create WCF services by invoking StructureMap.  However, it’s not using the main container to create instances.  It creates (and caches via a WCF context extension) a nested container, then utilizes the nested container to create the service.  This means the service, as well as anything the service directly or indirectly depends on (including ISession) will be disposed with the container.  The container is disposed when the WCF extension holding the nested container is removed from the instance context:

public class StructureMapChildContainerContext : IExtension<InstanceContext>
{
    public IContainer Container { get; set; }

    public StructureMapChildContainerContext(IContainer container)
    {
        Container = container;
    }

    public void Attach(InstanceContext owner)
    {
    }

    public void Detach(InstanceContext owner)
    {
        Container.Dispose();
    }
}

The net effect of this plumbing is that a child container is created at the beginning of each WCF method call and destroyed when the call ends.  With a little IoC wiring, we can now easily implement session-per-method-call:

public class NHibernateRegistry : Registry
{
    public NHibernateRegistry()
    {
        For<ISessionFactory>().Singleton().Use(NHibernateBootstrapper.CreateSessionFactoryForProduction);
        //ISession will be created only through child containers (one child container per request), so there's
        //no need to specify any additional scoping. 
        For<ISession>().Use(context =>
                                {
                                    var session = context.GetInstance<ISessionFactory>().OpenSession();
                                    session.FlushMode = FlushMode.Commit;
                                    return session;
                                });
    }
}

Notice how ISessionFactory is registered as a singleton, but ISession is not.  The ISessionFactory will *not* be recreated and disposed for each method call.  Instead, each call will utilize the singleton instance from the parent container.  The ISession, however, will be created and owned by the child container.  The child container will automatically create only a single instance for each method call (even if there are multiple types created that depend on ISession), and that instance will be disposed at the end of each request. 

Another nice benefit of this approach is that all disposable instances created in the fulfillment of a WCF method call will be disposed, not just the ISession.  I plan to change how I implement session-per-request in ASP.NET applications going forward as well so that I can reap the added benefits that nested containers provide.

Tags:

blog comments powered by Disqus