NHibernate session management in ASP.NET Web API

In this post I am going to describe a way to manage NHibernate sessions in WebAPI applications using 'session-per-action' pattern. This includes working NHibernate configuration that uses contextual sessions and SQL CE 4, simple model and mappings as well as some WebAPI specific bits (action filter and use of HttpConfiguration).

The problem

Accessing data using NHibernate usually consists of following steps:

  1. Creating a SessionFactory object.
  2. Opening a session.
  3. Accessing the data
  4. Closing the session

Regardless of your application type (be it a desktop or a web app) you will need a strategy to manage session lifecycle. This is no different in Web API.
Here are the most popular approaches I can think of (for web apps):

  1. create a new session every time you access data
  2. create session per each HTTP request
  3. create session per each action call (valid if using ASP.NET MVC / Web API)

Session per request

Creating sessions for each request can be accomplished by handling application's BeginRequest and EndRequest events in Global.asax.cs

public class WebApiApplication : System.Web.HttpApplication  
{
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        //Create session
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        //Close session
    }
// ...
}

This approach is quite common and there is a substantial amount of blog entries about it. The problem here is that the session will be created every time we make a HTTP request. Sometimes it may constitute a problem - for example if we serve static content (such as images) through ASP.NET pipeline - in this case the session will be created for each such request.

Going further - unauthorized calls will still cause session to be created, even though we don't really want or need that. For example this code would still create a session (and a transaction if  you wrapped your requests in a transaction as well)

[Authorize]  
public City Get(int id)  
{
}

Due to these issues it may be worth to consider managing sessions within a more granular scope.

Session per action

Alternatively we can create session object in the scope of an action. In ASP.NET MVC and Web API world this means creating action filter and using it to decorate actions or controllers (which will apply it to all actions).

[SessionManagement]  
public class CitiesController : ApiController  
{
    public IEnumerable<City> Get()
    {
    }
//...
}

Its worth to mention that session-per-action makes lazy loading in views impossible, because session will be closed before view templates are rendered. Lazy loading data in your views seems like a bad habit anyway and you should rather use flattened out view models instead.

Model and mappings

Let's start by creating a simple model that we will persist.

public abstract class Entity  
{
    public virtual int Id { get; protected set; }
}

public class City : Entity  
{
    public virtual long Population { get; set; }
    public virtual string Name { get; set; }
}

We also need mappings, I'm using NHibernate's own code mapping here (quite a fresh feature), but feel free to use anything else you prefer (eg. FluentNH or xml mappings).

public class CityMap : ClassMapping<City>  
{
    public CityMap()
    {
        Id(x => x.Id);
        Property(x => x.Population);
        Property(x => x.Name);
    }
}

Configuration

Besides that we also need NHibernate config that defines among other things, connection settings. I've decided to use Sql Server CE 4.0, but of course you can swap it for any other db engine supported by NHibernate (such as SQL Server).

Please note that in order for SQL Compact 4 to work you may need to add qualifyAssembly entry to web.config file (otherwise NHibernate will look for 3.5 assembly).

<assemblyBinding>  
 <qualifyAssembly
    partialName="System.Data.SqlServerCe" fullName="System.Data.SqlServerCe,
    Version=4.0.0.0, Culture=neutral,
    PublicKeyToken=89845dcd8080cc91"/>
</assemblyBinding>

Also note that we will be using NHibernate's contextual sessions (currentsessioncontext_class set to web). This will make make NHibernate use HttpContext to store current session.

<?xml version="1.0" encoding="utf-8" ?>  
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
    <property name="connection.connection_string">Data Source=|DataDirectory|\BlogSample.sdf</property>
    <property name="show_sql">true</property>
    <property name="current_session_context_class">web</property>
  </session-factory>
</hibernate-configuration>

In order to be able to instantiate session objects we need SessionFactory, which we are going to share among our web application (SessionFactory.GetSession() it is thread safe so no worries).

public class WebApiApplication : System.Web.HttpApplication  
{
    public static ISessionFactory SessionFactory
    {
        get;
        private set;
    }

    private void InitializeSessionFactory()
    {
        var nhConfig = new Configuration().Configure();
        var mapper = new ModelMapper();
        mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
        HbmMapping domainMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
        nhConfig.AddMapping(domainMapping);
        SessionFactory = nhConfig.BuildSessionFactory();
    }

    protected void Application_Start()
    {
        // ...
        InitializeSessionFactory();
    }
}

Session management action filter

Now we need to create action filter that will actually create a new session and bind it to the current context.

public class NhSessionManagementAttribute : ActionFilterAttribute  
{
    public NhSessionManagementAttribute()
    {
        SessionFactory = WebApiApplication.SessionFactory;
    }

    private ISessionFactory SessionFactory { get; set; }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var session = SessionFactory.OpenSession();
        CurrentSessionContext.Bind(session);
        session.BeginTransaction();
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var session = SessionFactory.GetCurrentSession();
        var transaction = session.Transaction;
        if (transaction != null && transaction.IsActive)
        {
            transaction.Commit();
        }
        session = CurrentSessionContext.Unbind(SessionFactory);
        session.Close();
    }
}

As a bonus we wrap actions in an explicit transaction, which is preferred over using implicit transactions.

Adding simple repository

Let's abstract persistence layer by adding a simple repository to our project. It will use SessionFactory.GetCurrentSession() to retrieve ISession bound to current context and created by our filter.

public class NHibernateRepository<TEntity>  
    : IRepository<TEntity> where TEntity : Entity
{
    private readonly ISession _session;
    public NHibernateRepository()
    {
        // you may want to use dependency injection instead
        _session = WebApiApplication.SessionFactory.GetCurrentSession();
    }

    public TEntity Get(int id)
    {
        return _session.Get<TEntity>(id);
    }

    public int Add(TEntity entity)
    {
        _session.Save(entity);
        return entity.Id;
    }

    public void Delete(TEntity entity)
    {
        _session.Delete(entity);
    }

    public void Update(TEntity entity)
    {
        _session.Update(entity);
    }

    public IQueryable<TEntity> Items
    {
        get { return _session.Query<TEntity>(); }
    }
}

 

Using HttpConfiguration

If you plan to use persistence layer (and sessions) in every controller of your api it may be worth leveraging WebAPI configuration capabilities to provide default behavior and eliminate need to manually decorate controllers.

protected void Application_Start()  
{
    //...
    InitializeSessionFactory();
    GlobalConfiguration.Configuration.Filters.Add(new NhSessionManagementAttribute());
}

 

Using it in controllers

Now we can easily use repositories and underlying session in our ApiController.

public class CitiesController : ApiController  
{
    public IEnumerable<City> Get()
    {
        return _repository.Value.Items.ToList();
    }
    //...
}

Hope this helps. You can view source code on bitbucket.org or get it using git:

git clone https://bitbucket.org/pwalat/piotrwalat.net.nhsessionmanagment.git
comments powered by Disqus