Apr 9, 2011

Lightweight NHibernate and ASP.NET MVC integration with Autofac

Many times when new project stars and we want to use NHibernate relatively a lot of work need to be done. Among them are:

  • Mapping of entities (I prefer automapping)
  • ISessionFactory singleton
  • ISession lifetime management (Per web request)
  • Transaction management

Sharp architecture project has all of them done and ready to use. But as for me this project has become too big and hard to understand. I wanted to have full control on what is happening in my app and didn’t want to have such a lot of abstractions. For example Repository and Entity objects that have inheritance chain about to 5 or 6 objects.

So I decided to show how very simple integration can be made with a minimum amount of code. To get all mentioned libraries I will use NuGet. We will need:

  • NHibernate
  • Fluent NHibernate
  • Autofac
  • Autofac.Mvc3

Lets start with mapping / session factory configuration:

public Configuration Configure()
{
    var configuration = new Configuration();
    configuration.SessionFactory()
                 .Proxy.Through<ProxyFactoryFactory>()
                 .Integrate.Using<MsSql2005Dialect>()
                 .Connected.ByAppConfing("dbConnection");


    FluentConfiguration fluentConfiguration = Fluently.Configure(configuration);
    fluentConfiguration.Mappings(map => map.AutoMappings.Add(
                                            new ModelGenerator().Generate()));

    return fluentConfiguration.BuildConfiguration();
}

public ISessionFactory GetSessionFactory()
{
    var configuration = Configure(); 
    return configuration.BuildSessionFactory();
}

Few things to note here. Combination of Loquacious and Fluent configuration is used because first one is supporting all NHibernate features, second one handles mappings integration. Also Model generator class is used:

private class ModelGenerator
{
    public AutoPersistenceModel Generate()
    {
        AutoPersistenceModel automap = new AutoPersistenceModel();

        automap.Conventions.AddFromAssemblyOf<ModelGenerator>();
        automap.UseOverridesFromAssemblyOf<ModelGenerator>();
        automap.AddEntityAssembly(Assembly.GetAssembly(typeof (Entity)))
            .Where(objectType => objectType.IsSubclassOf(typeof(Entity)));

        return automap;
    }
}

Here we setup location for the conventions, overriding's and entities. All classes that are inherited from Entity will be mapped. For conventions I’m using sharp architecture’s with small tweaks to have nice constraints names when generating schema from mappings:

public class ReferenceConvention : IReferenceConvention
{
    public void Apply(FluentNHibernate.Conventions.Instances.IManyToOneInstance instance)
    {
        string fkName = string.Format("{0}_{1}_FK", 
                                      instance.Name, instance.EntityType.Name);
        instance.ForeignKey(fkName);

        instance.Column(instance.Property.Name + "Fk");
    }
}

public class HasManyToManyConvention : IHasManyToManyConvention
{
    public void Apply(IManyToManyCollectionInstance instance)
    {
        string fkName = string.Format("{0}_{1}_FK", 
                                      instance.Member.Name, instance.EntityType.Name);
        instance.Key.ForeignKey(fkName);

        instance.Cascade.SaveUpdate();
    }
}

Now to the web part. In global asax on Application_Start event we need to setup Autofac and change the default controllers factory. To do this:

var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetAssembly(typeof (AuthorizationController)));
builder.Register(x => new NHibernateConfigurator().GetSessionFactory())
    .SingleInstance();
builder.Register(x => x.Resolve<ISessionFactory>().OpenSession())
    .InstancePerHttpRequest();

builder.RegisterModule(new AutofacWebTypesModule());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Code here is pretty clear. We setup ISessionFactory to be singleton, ISession instance is resolved by container and has PerHttpRequest lifestyle. Notice call of builder.RegisterModule that is going to add all the required http modules to support per web request lifestyle and change default controller factory to the one that uses Autofac. So now we are able to write code like this:

public class AuthorizationController : Controller
{
      private ISession session;

      public AuthorizationController(ISession session)
      {
          this.session = session;
      }

      public ActionResult Index()
      {
          var users = this.session.QueryOver<User>().List();
          return View(users);
      }
}

So we have controller that depends on ISession, which depends on ISessionFactory, which depends on our Nhibernate configurator class. Isn’t it kind from Autofac to handle all this? Smile

One last but important thing we need to do. Each call to the data base should be wrapped to correct transaction. You can read here why. The easiest way to handle this is to create action filter:

public class TransactionAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        DependencyResolver.Current.GetService<ISession>().BeginTransaction(IsolationLevel.ReadCommitted);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ITransaction currentTransaction = DependencyResolver.Current.GetService<ISession>().Transaction;

        if (currentTransaction.IsActive)
        {
            if (filterContext.Exception != null && filterContext.ExceptionHandled)
            {
                currentTransaction.Rollback();
            }
        }
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        ITransaction currentTransaction = DependencyResolver.Current.GetService<ISession>().Transaction;

        base.OnResultExecuted(filterContext);
        try
        {
            if (currentTransaction.IsActive)
            {
                if (filterContext.Exception != null && !filterContext.ExceptionHandled)
                {
                    currentTransaction.Rollback();
                }
                else
                {
                    currentTransaction.Commit();
                }
            }
        }
        finally
        {
            currentTransaction.Dispose();
        }
    }
}

Original implementation is taken from Sharp architecture and small changes made due to Autofac. So now out Index method should be marked with transaction attribute:

[Transaction]
public ActionResult Index()
{
    return View();
}

That's probably all we need to have NHibernate. As always source code is attached:

8 comments:

  1. good post, thanks

    ReplyDelete
  2. A timely post that just saved me some time!

    Thank you :-)

    ReplyDelete
  3. What if you have more than one controller that are involved in one webrequest?

    IMO one controller would dispose the shared session and the 2nd controller would throw an exception, because the session is disposed.

    ReplyDelete
  4. Each controller will have its own sesssion, autofac will handle that.

    ReplyDelete
  5. Just what I've been looking for - thanks!

    One question, I configure my fluent mappings like this:

    public ISessionFactory GetSessionFactory()
    {
    return
    Fluently.Configure().Database(
    MsSqlConfiguration.MsSql2008.ConnectionString(
    ConfigurationManager.ConnectionStrings["Staff3Sql"].ConnectionString)).Mappings(
    m => m.AutoMappings.Add(AutoMap.AssemblyOf)).BuildSessionFactory();
    }

    If I use the same method as you I get no results, will I be causing problems with the DI using this approach?

    Cheers

    Rowan

    ReplyDelete
  6. Not sure what you mean by empty results. If your ISessionFactory is singletone and session is per web request, everything should be fine.

    ReplyDelete
  7. If the transaction attribute is not present, I am still getting persistence. Should it work that way? Doesnt seem like it should.

    ReplyDelete
  8. When you do not wrap in transaction, Nhibernate falls into implicit transaction mode. It will work, but it is concidered to be a really bad practice. You can read about it here http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions

    ReplyDelete