Continuing to play with Ninject and MVC3, I wanted to explore further how Ninject can help manage objects over the life of a web request. One of the great things about Ninject is that it allows you to easily change the scope in which objects live through use of Activation Behaviours.
Behave Yourself!
Using Ninject, you can control when new instances of objects are created with Activation Behaviours (although this documentation appears to be out of date at the time of writing).
To set the activation behaviour of a binding, you append a call like so:
Bind().To().InTransientScope();
Ninject has the following scopes built in:
- TransientScope: A new instance is created for each request.
- SingletonScope: Only a single instance of an object is created, and the same instance is returned every time.
- ThreadScope: A new instance is created for each thread.
- RequestScope: A new instance is created for each web request.
RequestScope and MVC
What I was interested in was whether the RequestScope would create a new instance of something at the start of a web request, and then immediately clean said object up at the end of the request, so that objects wouldn't be kept around longer than they were needed.
A simple test ensued:
public class MvcApplication : NinjectHttpApplication
{
private static ILog logger = log4net.LogManager.GetLogger("default");
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
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 = UrlParameter.Optional } // Parameter defaults
);
}
protected override Ninject.IKernel CreateKernel()
{
return new StandardKernel(new MyModule());
}
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}
public class MyModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind().To().InRequestScope();
}
}
public interface ITestInterface { }
public class TestClass : ITestInterface, IDisposable
{
public void Dispose()
{
System.Console.WriteLine("Disposing");
}
}
public class HomeController : Controller
{
ITestInterface test;
public HomeController(ITestInterface test)
{
this.test = test;
}
public ActionResult Index()
{
return View();
}
}
All I did here was setup a controller that would take an instance of a disposable test class. We know it gets created and passed to the controller, so all I wanted to see was that it was disposed at the end of the request. I did this by setting a breakpoint in the dispose method of the class and debugging through Visual Studio.
If you run up the project, you'll see that the breakpoint gets hit after the request has been served, nice and promptly; exactly what I was hoping for.
Making NHibernate Behave
The recommended way or using NHibernate with MVC is to have a single session created per request. That is each time a client makes a request for a page, a new session is opened and kept open until the request is complete and data is returned to the client.
Sounds a little like the RequestBehaviour activation type fits perfectly here doesn't it?
To test this, we need to setup NHibernate and create a simple user class and repository. So to start, here's how I like to get my session factory:
namespace DITest
{
public static class SessionFactoryProvider
{
private static ISessionFactory sessionFactory;
private static Object lockObj = new Object();
private static void BuildSessionFactory()
{
string connStr = ConfigurationManager.ConnectionStrings["SqlServer"].ConnectionString;
sessionFactory = Fluently.Configure().
Database(MsSqlConfiguration.MsSql2005.ConnectionString(connStr)).
ExposeConfiguration(c => c.Properties.Add("hbm2ddl.keywords", "none")).
Mappings(m => m.FluentMappings.AddFromAssemblyOf()).
BuildSessionFactory();
}
public static ISession OpenSession()
{
if (sessionFactory == null)
lock(lockObj)
if (sessionFactory == null)
BuildSessionFactory();
return sessionFactory.OpenSession();
}
}
}
And this is registered with Ninject like so:
Bind().ToMethod(x => SessionFactoryProvider.OpenSession()).InRequestScope();
Next, a simple user class, mapping (I'm using FluentNhibernate here to do the mapping) and repository (all in one place for brevity):
namespace DITest
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int Age { get; set; }
}
public class UserMap : ClassMap
{
public UserMap()
{
Id(c => c.Id);
Map(c => c.Name);
Map(c => c.Age);
}
}
public class UserRepository
{
ISession session;
private IQueryable Users
{
get { return session.Query(); }
}
public UserRepository(ISession session)
{
this.session = session;
}
public IList GetUsers()
{
using (session.BeginTransaction())
{
var query = from c in Users select c;
return query.ToList();
}
}
}
}
Finally, updated the controller to pull some information from the database:
public class HomeController : Controller
{
UserRepository repo;
public HomeController(UserRepository userRepo)
{
repo = userRepo;
}
public ActionResult Index()
{
ViewBag.Users = repo.GetUsers();
return View();
}
}
To verify this was all working correctly and the session is being closed properly, I setup logging to log debug information, which churns out tons of useful information:
Then after running up the page and doing a request, we can look at the generated log and see what's been happening:
14/07/11 12:42:50 DEBUG [session-id=6bf6823c-44d1-4a15-888e-c858a9212024] opened session at timestamp: 634462405708, for session factory: [/b9e13234ae244e18b7f2ce1923c58e48]
14/07/11 12:42:50 DEBUG Begin (Unspecified)
14/07/11 12:42:50 DEBUG Obtaining IDbConnection from Driver
...//edited for brevity
14/07/11 12:42:50 DEBUG IDbTransaction disposed.
14/07/11 12:42:50 DEBUG transaction completion
14/07/11 12:42:50 DEBUG aggressively releasing database connection
14/07/11 12:42:50 DEBUG Closing connection
14/07/11 12:42:50 DEBUG [session-id=6bf6823c-44d1-4a15-888e-c858a9212024] running ISession.Dispose()
14/07/11 12:42:50 DEBUG [session-id=6bf6823c-44d1-4a15-888e-c858a9212024] executing real Dispose(True)
14/07/11 12:42:50 DEBUG closing session
14/07/11 12:42:50 DEBUG running BatcherImpl.Dispose(true)
I've cut out a lot of the logging that just details queries are happening so that we can see what we care about; that the session is being closed properly after the requests have happened, and not before!
Summary
We looked at the various types of activation behaviour that Ninject supports, delved deeper into RequestBehaviour and verified that it is in fact doing what it says on the tin, and then saw how this is useful when working with MVC and NHibernate.
So now we have an NHibernate session that will last for the duration of a request. What would be really useful now is some concept of a unit of work, to allow multiple Nhibernate requests to be batched into a single transaction. Stay tuned!