Monday, February 22, 2010

Creating an ASP.NET MVC 2 Controller Factory with Ninject

Introduction



Recently I've been getting into Test Driven Development and one useful thing that helps me out with this is making use of the Inversion of control principle. This makes it easy to pass a Mock version of all my services for the purposes of testing.

However this presents a challenge with the standard MVC Controller Factory. It only supports the parameterless contructors. However, one of the great things about ASP.NET MVC (2) is that it allows you to override most aspects of the framework. It doesn't restrict you as much as it's ASP.NET webforms predecessor. So to avoid the so call "Anti Pattern" of having both a Parameterless Constructor for your "Real Services" and an overloaded constructor for your testing/Mock Services, you can follow this guide to create a Controller Factory which uses ninject. Then you'll only have 1 constructor and you'll sleep soundly.

Creating Ninject Controller Factory




  • Now let's create a new MVC 2 Project
  • Once it open's we'll need to add a reference to ninject.core.dll

  • Next, let's create a helper method for our ninject controller factory called NinjectControllerFactory.cs

  • We'll need to inherit from the default controller factory. Mainly so we don't have to re-invent the wheel and we can just override one part of it.

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

namespace NinjectIoc
{
    public class NinjectControllerFactory : DefaultControllerFactory
    {
    }
}

  • For the purposes of this demo we'll need to create an example service and interface, to demo the Ioc process and ninject.

namespace ExampleServices
{
    public interface IExampleInterface
    {
        void Method1();
        string Property1 { get; }
    }

    public class ExampleConcreteClass : IExampleInterface
    {

        #region IExampleInterface Members

        public void Method1()
        {
            // Do Nothing.
        }

        public string Property1
        {
            get
            {
                return "Hello World";
            }
        }

        #endregion
    }
}
  • Now we can setup a ninject Module which returns our ExampleConcreteClass when given the IExampleInterface.
  • We do this by creating a class which inherits from ninjects standardmodule and overrides the Load Method

private class ExampleConfigModule : StandardModule
    {
        public override void Load()
        {
            Bind<IExampleInterface>().To<ExampleConcreteClass>();
        }
    }
  • Finally we'll override the GetControllerInstance Method from the DefaultControllerFactory using ninject to return a controller complete with any parameters in the controllers constructor.
public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel kernel = new StandardKernel(new ExampleConfigModule());

        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            // We don't want to pass null to ninject as we'll get a strange error.
            return controllerType == null ? null
                                          : (IController)kernel.Get(controllerType);
        }
    }
  • Now we'll need to tell the MVC 2 framework to use our new Controller Factory insted of the defaultControllerFactory.
  • To do this we'll need to edit the global.asax.cs file
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
            );

            // Setup Our new Controller Factory.
            ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());

        }
  • That's it for the setup. Now we'll need to test it out.
  • Let's update the default Home Controller to include a parameter in it's constructor
public class HomeController : Controller
    {
        #region Testing Our Controller Factory

        private IExampleInterface _example;

        public HomeController(IExampleInterface exampleInterface)
        {
            // Test our our new Ninject Controller Factory
            _example = exampleInterface;
        }

        #endregion

        public ActionResult Index()
        {
            // Will it work?? 
            ViewData["Message"] = _example.Property1;

            return View();
        }

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

  • Now if this work's we should see the word's "Hello World" on the home page.

  • Cool. it worked.

Conclusion


It would be very easy to swap out Ninject for another Ioc container. So i hope it helps someone out there. If you want to get a copy of the example code then I've copied it up to my



Enjoy.

3 comments:

  1. I'm not able to download a copy of your example, can you check that your link is good please ?

    ReplyDelete
  2. Hey.. thanks for that. I've updated the link and it works ok now.. please try again ;)

    ReplyDelete
  3. Great post, still cannot get why "Type controllerType" may be null?

    ReplyDelete