Aug 6 2009

Easiest way to hook up ASP.NET MVC with NHibernate

Category: zvolkov @ 20:27

Today I was prototyping yet another data-driven admin UI using ASP.NET MVC and NHibernate. Naturally, before I could do it my way I had to look around at things like S#arp Architecture (too bloated), Subsonic (didn't work with my legacy DB), and Ayende's latest post on the topic (too simplistic). Combining them together produced the kind of design I wanted -- very simple and flexible enough. Here's how. Accessible to absolute beginners, I promise. This post assumes you are familiar with NHibernate and with concept of IoC controllers and that you have your NHibernate entities and mappings all ready and defined (remember, the topic of this post is Hooking Up, not NHibernate 101).

Alright, without further disclaimers, follow on:

  1. Download ASP.NET MVC Release
  2. Create new ASP.NET MVC project (use File --> New Project, not File --> New Web Site)
  3. Download Castle Windsor 2.0, and reference all 4 DLLs in your web project.
  4. Download MVCContrib and reference MvcContrib.Castle.dll
  5. Download NHibernate 2.1 and reference NHibernate.dll and NHibernate.ByteCode.Castle.dll
  6. Reference your assembly with NHibernate mapping HBM files embeded as resources (e.g. Your.Assembly.With.NHibernate.Mappings.dll)

Here's the code that goes to Global.asax.cs:

using System;
using System.IO;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.Facilities.FactorySupport;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Sample.MvcApplication.Controllers;
using MvcContrib.Castle;
using NHibernate;
using NHibernate.Context;

namespace Sample.MvcApplication
{
    public class MvcApplication : System.Web.HttpApplication
    {
        public static IWindsorContainer Container;     // these two don't have to be public
        public static ISessionFactory SessionFactory;  // but I opened them up in case I'll need them somewhere else

        protected void Application_Start()
        {
            //make ASP.NET use castle container to create controllers
            //this way we can inject NHibernate session into controllers

            //create empty container
            Container = new WindsorContainer(); 

            //find all controllers in current assembly and add them to container
            Container.RegisterControllers(typeof(HomeController).Assembly); 

            //tell ASP.NET to get its controllers from Castle
            ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container)); 

            //initialize NHibernate from NHibernate.config
            SessionFactory = new NHibernate.Cfg.Configuration().Configure( Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "NHibernate.config"  ) ).BuildSessionFactory();
            //register Factory facility - required for UsingFactoryMethod()
            Container.AddFacility<FactorySupportFacility>();
            //tell container to return SessionFactory.GetCurrentSession() every time ISession is requested
            Container.Register(Component.For<ISession>().UsingFactoryMethod(() => SessionFactory.GetCurrentSession()).LifeStyle.Transient);

            //register MVC routes (standard part of any ASP.NET MVC application)
            RegisterRoutes(RouteTable.Routes);
        }

        protected void Application_OnEnd()
        {
            //dispose Castle container and all the stuff it contains
            Container.Dispose();
        }

        protected void Application_BeginRequest()
        {
            //start new NHibernate session on each web request
            var session = SessionFactory.OpenSession();
            //bind session to the thread so all the code can access it using SessionFactory.GetCurrentSession()
            //this relies on "current_session_context_class" property set to "web" in NHibernate.config
            CurrentSessionContext.Bind(session); 
        }

        protected void Application_EndRequest()
        {
            //unbind from the thread
            //no need to close the session as it is already automatically closed at this point (not sure why)
            CurrentSessionContext.Unbind(SessionFactory);
        }

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default",                                              // Route name
                "{controller}/{action}/{id}",                           // URL with parameters
                new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
            );
        }
    }
}

Add file NHibernate.config and configure it approximately like so:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.connection_string">Server=XXX;Database=YYYY;Trusted_Connection=False;User ID=AAAA;Password=BBBB;</property>
    <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="cache.use_second_level_cache">false</property>
    <property name="cache.use_query_cache">false</property>
    <property name="show_sql">false</property>
    <property name="adonet.batch_size">16</property>
    <property name="default_schema">dbo</property>
    <property name="generate_statistics">false</property>
    <property name="command_timeout">300</property>
    <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
    <property name="current_session_context_class">web</property>
    <mapping assembly="Your.Assembly.With.NHibernate.Mappings"/>
  </session-factory>
</hibernate-configuration>

 

All you need to do now is inject NHibernate ISession into your controllers (this is done automatically by WindsorControllerFactory, just add ISession parameter to controller constructor) and off you go:

using System.Web.Mvc;
using Fortigent.CDI.Common;
using NHibernate;

namespace Sample.MvcApplication.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        private ISession db;

        public HomeController(ISession db)
        {
            this.db = db;
        }

        public ActionResult Index()
        {
            using(var tran = db.BeginTransaction())
            {
                var name = db.Get<Customer>(123).Name;

                ViewData["Message"] = "Welcome to ASP.NET MVC! " + name;

                tran.Commit();

                return View();
            }
        }

        public ActionResult About()
        {
            return View();
        }
    }
}

No changes to Web.Config, just compile and run. Like I promised, piece of cake!

Tags:

Comments

1.
trackback progg.ru says:

Простой пример связки ASP.NET MVC и NHibernate

Thank you for submitting this cool story - Trackback from progg.ru

2.
Rasmus Christensen Rasmus Christensen says:

Just solved the problem. Build the source and reference version 2.0 Smile

3.
Roberto Roberto says:

This way to manage Isession, support multi-threads?

4.
zvolkov zvolkov says:

Roberto, yes it is thread-safe in a limited fashion: a separate NH session will be created for each HTTP request. However if you want to use ThreadPool or other multithreading techniques inside the logic that handles each request, you won't get thread-safety for free. NH Sessions is not designed to be used across multiple threads.

5.
Joshua Angulo Joshua Angulo says:

Perfect.  I had the same opinions about those architectures.  This is perfect.  The one thing that I did a little different was use Fluent for my nhib mapping.  Exactly what I was looking for.  Thank you.

6.
reggieboy reggieboy says:

Hello,

Im am new to nhibernate and currently learning about how to manage sessions in an ASP.NET MVC application, and this post give me an idea how to do so.

But i have a question? How can i handle a long conversations?  

Comments are closed