Home / .NET / Official Ninject MVC extension gets support for MVC3

Official Ninject MVC extension gets support for MVC3

MVC3 added support for Dependency Injection frameworks. To take advantage of these features I did a completely new implementation of the Ninject.Web.Mvc extension. Unlike other existing MVC3 implementations for Ninject, this extension goes further than just adding a IDependencyResolver for Ninject. It has tightly Ninject integrated support for various things such as Filters and Validators.

This post assumes that you have a basic knowledge about MVC, Ninject and Dependency Injection in general.

Let’s get started
First you have to get a complied version of the extension. You either have to get the sources or the binaries from Github. The binaries are also available from the build server. The sources also come with a sample project that demonstrates all features.

The next step is to setup the application to use the Ninject extension. Therefore change the global.asax to use NinjectHttpApplication instead of HttpApplication and override CreateKernel to create a kernel and load all the modules that you need in your application. One way to load all modules is to tell Ninject to load all modules from your application assemblies. Here is an example of the global.asax.

public class MvcApplication : NinjectHttpApplication
{
   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
           });
   }

   protected override IKernel CreateKernel()
   {
       var kernel = new StandardKernel();
       kernel.Load(Assembly.GetExecutingAssembly());
       return kernel;
   }

   protected override void OnApplicationStarted()
   {
       base.OnApplicationStarted();

       AreaRegistration.RegisterAllAreas();
       RegisterGlobalFilters(GlobalFilters.Filters);
       RegisterRoutes(RouteTable.Routes);
   }
}

Dependency Injection for Controllers
In the previous step we already prepared anything that is necessary for controller injection. The constructor of controllers can now be changed to accept dependencies. The only thing that has to be done is to configure the Ninject bindings for its dependencies. The controller itself will be found by Ninject even without adding a binding. Of course, you can still add a binding for the controller in case you need to specify more information for the binding (e.g. an additional constructor argument).

Here is an example of a controller that has the welcome message service as dependency.

public class HomeController : Controller
{
    private readonly IWelcomeMessageService welcomeMessageService;

    public HomeController(IWelcomeMessageService welcomeMessageService)
    {
        this.welcomeMessageService = welcomeMessageService;
    }

    public void Index()
    {
        ViewModel.Message = this.welcomeMessageService.TodaysWelcomeMessage;
        return View();
    }
}

public class WelcomeMessageServiceModule : NinjectModule
{
    public override void Load()
    {
        this.Bind<IWelcomeMessageService>().To<WelcomeMessageService>();
    }
}

Dependency Injection for filters
With MVC3 I introduced a completely new pattern to configure filters for controllers and its actions. While injection of filter attributes is still supported I recommend using this new pattern for filter configuration because it has the advantage to support constructor injection and does not require the InjectAttribute anymore.

But let’s see how this new pattern works. First of all you have to create your filter class by implementing one of the filter interfaces e.g. IActionFilter. All the dependencies are added to the constructor. The following example of a logging filter has a logger as dependency and can be configured with the level that is used to log.

public class LogFilter : IActionFilter
{
    private readonly ILog log;
    private readonly Level logLevel;

    public LogFilter(ILog log, Level logLevel)
    {
        this.log = log;
        this.logLevel = logLevel;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var message = string.Format(
                CultureInfo.InvariantCulture,
                "Executing action {0}.{1}",
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                filterContext.ActionDescriptor.ActionName),
        this.log.Logger.Log(typeof(LogFilter), this.logLevel, message, null);
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var message = string.Format(
                CultureInfo.InvariantCulture,
                "Executed action {0}.{1}",
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                filterContext.ActionDescriptor.ActionName),
        this.log.Logger.Log(typeof(LogFilter), this.logLevel, message, null);
    }
}

To apply this filter to an action or controller we need to specify a binding. But unlike other bindings filters require that BindFilter is used instead of the normal Bind. Additionally to the type of the filter you have to specify the filter scope and the filter order. More information about these two parameters can be found Brad Wilsons Blog.

In the example below the LogFilter is applied to every action and configured with the log level info.

public class LoggingModule : NinjectModule
{
    public override void Load()
    {
        this.Bind<ILog>().ToMethod(GetLogger);
        this.BindFilter<LogFilter>(FilterScope.Controller, 0)
            .WithConstructorArgument("logLevel", Level.Info);
    }

    private static ILog GetLogger(IContext ctx)
    {
        var filterContext = ctx.Request.ParentRequest.Parameters
                      .OfType<FilterContextParameter>().SingleOrDefault();
        return LogManager.GetLogger(filterContext == null ?
            ctx.Request.Target.Member.DeclaringType :
            filterContext.ActionDescriptor.ControllerDescriptor.ControllerType);
    }
}

Conditional bindings for filters

Usually, a filter is applied to a few actions. With the MVC3 extension for Ninject this is done by adding a condition to the filter binding. The available overloads of “When” are a bit different than for other Ninject bindings. Blow you find examples of the available When overloads.

// LogFilter is applied to controllers that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Controller, 0)
     .WhenControllerHas<LogAttribute>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to actions that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenActionHas<LogAttribute>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to all actions of the HomeController
this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenControllerTypeIs<HomeController>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to all Index actions
this.BindFilter(FilterScope.Action, 0)
     .When((controllerContext,  actionDescriptor) =>
                actionDescriptor.ActionName == "Index")
     .WithConstructorArgument("logLevel", Level.Info);

Filter Configurations

In the previous examples you have already seen that a filter can be configured by adding one or more With statements to the binding configuration. But some times it is necessary to have different configurations for different actions. In this case it’s possible to get the configuration value from an attribute of the action or controller. The next example shows all new With overloads that come with the MVC3 extension.

[Log(LogLevel = Level.Debug)]
void Index() {}

this.BindFilter<LogFilter>(FilterScope.Controller, 0)
     .WhenControllerHas<LogAttribute>()
     .WithConstructorArgumentFromControllerAttribute<LogAttribute>(
          "logLevel",
          attribute => attribute.LogLevel);
// For property injection WithPropertyValueFromControllerAttribute instead

this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenActionHas<LogAttribute>()
     .WithConstructorArgumentFromActionAttribute<LogAttribute>(
          "logLevel",
          attribute => attribute.LogLevel);
// For property injection WithPropertyValueFromActionAttribute instead

this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenActionHas<LogAttribute>()
     .WithConstructorArgument((
          "logLevel",
          (context, controllerContext, actionDescriptor) =>
               actionDescriptor.ActionName == "Index" ? Level.Info : Level.Warn);

Injection of Filter Attributes
As already mentioned, the extension supports injection of filter attributes. But unlike with the Ninject configured attributes it is restricted to property injection. That’s the reason why I recommend not to use this feature. It is only there for backward compatibility. Here is an example of the log filter as filter attribute.

public class LogFilterAttribute : ActionFilterAttribute
{
    [Inject]
    public ILog Log
    {
        get; set;
    }

    public Level LogLevel
    {
        get; set;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var message = string.Format(
                CultureInfo.InvariantCulture,
                "Executing action {0}.{1}",
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                filterContext.ActionDescriptor.ActionName),
        this.Log.Logger.Log(typeof(LogFilter), this.LogLevel, message, null);
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var message = string.Format(
                CultureInfo.InvariantCulture,
                "Executed action {0}.{1}",
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                filterContext.ActionDescriptor.ActionName),
        this.Log.Logger.Log(typeof(LogFilter), this.LogLevel, message, null);
    }
}

Injection of Validators
Another new feature of the MVC3 extension is the support for injection of validation attributes. But as with all other attributes it only allows property injection. The following example demonstrates how to create a zip code validation attribute that uses a service to check if the value is valid and that the zip code exists.

public class ZipCodeAttribute : ValidationAttribute
{
    [Inject]
    public IZipCodeService ZipCodeService { get; set; }

    public override bool IsValid(object value)
    {
        if (value == null)
        {
            return true;
        }

        if ((value is string) && string.IsNullOrEmpty((string)value))
        {
            return true;
        }

        var zipCode = Convert.ToInt32(value);
        return this.ZipCodeService.IsValidZipCode(zipCode);
    }
}

What’s more?
Because the MVC3 extension is based on the IDependencyResolver of MVC3 it is also possible to replace most of the involved components such as the view engine, view page activator, model binder, and more. Brad Wilsons Blog post gives an excelent introduction what can be done with MVC3.