Wednesday, August 18, 2010

Using Ninject as your Dependency Injection container in ASP.NET MVC 3

MVC 3 Preview 1 has been available for a few weeks now from Microsoft, with Preview 2 scheduled for release sometime next month.

As a web development framework, MVC 3 is pretty cool - simple to set up and start using, with a terse, clean syntax courtesy of the new Razor view engine. Coupled with Entity Framework 4 (supporting both code-first generation of database schemas and wrapping existing database schemas), MVC 3 + EF 4 has the makings of a very good web development stack.

If you're interested in using Ninject as the Dependency Injection (DI) container in MVC 3, then you'll find the code below interesting - I couldn't find this anywhere else on the web so ended up writing it. It's the required implementation of the System.Web.Mvc.IMvcServiceLocator that gets instantiated and used in the Application_Start method in Global.asax.cs.

Using DI with MVC 3 makes a lot of sense - we use it to decouple concrete implementations from the interface that we code against so that we can quickly swap in alternate implementations, e.g. a quick, self-contained in-memory database for unit testing using Moq or similar.

This link from Brad Wilson shows how to set up Microsoft Unity as the dependency injection container and this presentation from Phil Haack gives a fleeting, tantalising glimpse of how the Ninject equivalent might look but there's nowhere to get the complete code you need to get it working!

So I put the two together in order to use Ninject as my DI container. Here's the code (with zero comments as per my normal coding standard):

using System.Web.Mvc;
using System;
using System.Collections.Generic;
using Ninject;

namespace AdminApp.Models
{

public class NinjectMvcServiceLocator : IMvcServiceLocator
{
public IKernel Kernel { get; private set; }

public NinjectMvcServiceLocator(IKernel kernel)
{
Kernel = kernel;
}

public object GetService(Type serviceType)
{
try
{
return Kernel.Get(serviceType);
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}


public IEnumerable<tservice> GetAllInstances<tservice>()
{
try
{
return Kernel.GetAll<tservice>();
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}

public IEnumerable<object> GetAllInstances(Type serviceType)
{
try
{
return Kernel.GetAll(serviceType);
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}

public TService GetInstance<tservice>()
{
try
{
return Kernel.Get<tservice>();
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}

public TService GetInstance<tservice>(string key)
{
try
{
return Kernel.Get<tservice>(key);
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}

public object GetInstance(Type serviceType)
{
try
{
return Kernel.Get(serviceType);
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}

public object GetInstance(Type serviceType, string key)
{
try
{
return Kernel.Get(serviceType, key);
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}


public void Release(object instance)
{
try
{
Kernel.Release(instance);
}
catch (Ninject.ActivationException e)
{
throw new System.Web.Mvc.ActivationException("PAK", e);
}
}



}
}





And here's how to instantiate and use it in Global.asax.cs:

var kernel = new StandardKernel(new NinjectRegistrationModule());
var locator = new NinjectMvcServiceLocator(kernel);
MvcServiceLocator.SetCurrent(locator);


Finally, here's a sample NinjectRegistrationModule which maps the implementation I want onto the generic interface that my code consumes:

using Ninject.Modules;
using AdminApp.Controllers;

namespace AdminApp
{
class NinjectRegistrationModule : NinjectModule
{
public override void Load()
{
Bind<ISpecialRepository>().To<DbSpecialRepository>().InRequestScope();
}
}
}

2 comments:

Googler said...

It appears ActivationException is deprecated from the CTP into the Beta. What would you recommend instead in the Beta+?

Humphrey Sheil said...

Hi

I'd probably start by reading the release notes for the CTP and then the relevant method sigs for the key interfaces I used to see what new Exception to use.