Part 7: NHibernate and Ninject for ASP.NET MVC
In part 6, I explained how to set up Ninject with ASP.NET MVC. In this part, we’ll add NHibernate to the mix. Specifically, we’re going to set up session-per-request using a Ninject and bind all the necessary NHibernate interfaces.
Of course, for the sake of history, read up on part 1, part 2, part 3, part 4, part 5, and part 6.
If you aren’t familiar with NHibernate in an ASP.NET MVC application, the most common way to manage your sessions is to open one session per web request. Just about everything you need to know about session-per-request is explained in the content and comments of this post on Ayende’s blog, but I’ll summarize for you.
- While building a session factory may be a big operation, once it’s built, opening a session is lightweight.
- Opening a session does not open a connection to the database
- NHibernate has a built in method for doing session-per-request, but Ayende doesn’t use it for simple stuff and neither will we. When your application doesn’t do anything other than session-per-request, it’s just easier to do it this way.
- Multiple business transactions and therefore multiple sessions in a single web request are usually not necessary, just because of how users tend to interact with the application. Even then, you can usually accomplish the same thing with multiple DB transactions on the same session.
NHibernate Burrow is available to help with complex session management in web apps where session per conversation is used. Basically, this allows you to span your NHibernate sessions across several web requests. Just a quick note: If you disregarded everyone’s advice and used Identity (integer auto-number) ID fields, Burrow won’t work for you. If you want more information, check out the Burrow posts on NHForge. Also, Jose Romaniello’s uses Conversation per Business Transaction in his NHibernate and WPF series on NHForge.org. It’s definitely worth a read.
OK. Back to session-per-request. I’m taking a slightly different approach than Ayende. Even though opening a session is lightweight, I don’t like the idea of opening a session for requests that may not use NHibernate at all. For example, in an application I’m building at work, only about 7 views out of nearly 50 actually use an NHibernate session. That’s a lot of unused sessions.
First things first, we need to make a Ninject module for all of our NHibernate bindings. Where are we going to put it? We have two options. We could put it in NStackExample.Data with all of our NHibernate mappings and configuration. We could also put it in NStackExample.Web. Like Ayende, we will be storing the NHibernate session in the context of the current web request and relying on our application’s EndRequest event to close the session. Since we’re unfortunately coupled to the web application, we’ll put it in the web project.
- In the web project, make a new folder called Code.
- Make a class in that folder called NHibernateModule.
- NHibernateModule should inherit from Ninject.Core.StandardModule.
The process of configuring NHibernate is a lot of work and only needs to be done once. Since our configuration object also creates the session factory, another potentially heavy operation, we kill two birds with one stone. The binding for our NHibernate configuration looks like this:
Public Overrides Sub Load() Dim Cfg As New NStackExample.Data.Configuration Cfg.Configure() Bind(Of NStackExample.Data.Configuration).ToConstant(Cfg) End Sub
public override void Load() { NStackExample.Data.Configuration Cfg = new NStackExample.Data.Configuration() Cfg.Configure(); Bind().ToConstant(Cfg); }
ToConstant bindings essentially create singletons, at least within the scope of our Ninject kernel. Unlike true singletons, this isn’t evil because our tests are free to mock, replace, and re-implement them as necessary.
Now that we have NHibernate configured and our session factory built, we need to bind our NHibernate session. The scope of our session is somewhat complex (per-request). We could use the OnePerRequestBehavior of Ninject, but that requires the registration of an IIS HTTP module. Instead, we’ll just bind it to a method and manage it ourselves. This method will create up to one session per request. If a particular request doesn’t require a session, Ninject will never call the method, so an unnecessary session won’t be created. If a particular request asks for a session more than once, perhaps to build more than one DAO, the method will create a single session and use it throughout the web request. Here’s what our module looks like with the binding for our session:
Friend Const SESSION_KEY As String = "NHibernate.ISession" Public Overrides Sub Load() Dim Cfg As New Configuration Cfg.Configure() Bind(Of Configuration).ToConstant(Cfg) Bind(Of NHibernate.ISession).ToMethod(AddressOf GetRequestSession) End Sub Private Function GetRequestSession(ByVal Ctx As IContext) As NHibernate.ISession Dim Dict As IDictionary = HttpContext.Current.Items Dim Session As NHibernate.ISession If Not Dict.Contains(SESSION_KEY) Then 'Create an NHibernate session for this request Session = Ctx.Kernel.Get(Of Configuration)().OpenSession() Dict.Add(SESSION_KEY, Session) Else 'Re-use the NHibernate session for this request Session = Dict(SESSION_KEY) End If Return Session End Function
internal const string SESSION_KEY = "NHibernate.ISession"; public override void Load() { Configuration Cfg = new Configuration(); Cfg.Configure(); Bind<Configuration>().ToConstant(Cfg); Bind<NHibernate.ISession>().ToMethod(x => GetRequestSession(x)); } private NHibernate.ISession GetRequestSession(IContext Ctx) { IDictionary Dict = HttpContext.Current.Items; NHibernate.ISession Session; if (!Dict.Contains(SESSION_KEY)) { // Create an NHibernate session for this request Session = Ctx.Kernel.Get<Configuration>().OpenSession(); Dict.Add(SESSION_KEY, Session); } else { // Re-use the NHibernate session for this request Session = (NHibernate.ISession) Dict[SESSION_KEY]; } return Session; }
All we have left to do is dispose our session at the end of the request. Let's go back to the Global.asax codebehind.
Private Sub MvcApplication_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.EndRequest If Context.Items.Contains(NHibernateModule.SESSION_KEY) Then Dim Session As NHibernate.ISession = Context.Items(NHibernateModule.SESSION_KEY) Session.Dispose() Context.Items(NHibernateModule.SESSION_KEY) = Nothing End If End Sub
public MvcApplication() { this.EndRequest += MvcApplication_EndRequest; } private void MvcApplication_EndRequest(object sender, System.EventArgs e) { if (Context.Items.Contains(NHibernateModule.SESSION_KEY)) { NHibernate.ISession Session = (NHibernate.ISession) Context.Items[NHibernateModule.SESSION_KEY]; Session.Dispose(); Context.Items[NHibernateModule.SESSION_KEY] = null; } }
To illustrate how this will work, I’ve made several additions to the code download. I’ve added a BaseController and HomeController so we can begin to run our web application. I’ve also added a IStudentDao and ICourseDao interfaces to the core project and corresponding implementations in the Data project. I’ve bound the DAO interfaces to their corresponding implementations and added debug statements to output exactly what’s happening with our session. Finally, I’ve set up a constructor in HomeController making it dependent on IStudentDao and ICourseDao.
When we run our application, we see from the debug output that the session is created when we create our IStudentDao. The session is reused to create our ICourseDao. This gives us everything we need to create the HomeController. The web request executes. When the request ends, the session is disposed. If you remove one of the Dao dependencies from HomeController, you’ll see that our session is created. It’s not reused because nothing else needs a session. If you remove both of the Dao dependencies from HomeController, you’ll see that our session is never even created. Since we didn’t create a session, we don’t dispose it when the web request ends.
That’s all for part 7. In part 8, we’ll wrap the NHibernate transaction for use in our controllers project and build a real DAO or two.
Get your code here! We have VB.NET and CSharp flavored bits.
Jason
- NHibernating Ninja Wannabe