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:
- Download ASP.NET MVC Release
- Create new ASP.NET MVC project (use File --> New Project, not File --> New Web Site)
- Download Castle Windsor 2.0, and reference all 4 DLLs in your web project.
- Download MVCContrib and reference MvcContrib.Castle.dll
- Download NHibernate 2.1 and reference NHibernate.dll and NHibernate.ByteCode.Castle.dll
- 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: