Sunday 2 October 2011

Dependency Injection for Filters with Ninject in MVC3

Tools used when writing this article: MvcScaffolding ver 1.0.0, Ninject ver 2.2.1.4 and Ninject.MVC3 ver 2.2.2.0.

There are three ways (as far as I know) to do dependency injection for filters in MVC3 using Ninject.

On this post, we'll see the preferred way first. The second way which uses filter attribute is described on my next post. While the third one which should only be used for backward compatibility can be seen on my other post.

First, we create a filter class that inherits from one of these four interfaces; IAuthorizationFilter, IActionFilter, IResultFilter or IExceptionFilter. Each interface requires us to implement particular methods defined for it. For example when we use IActionFilter, we need to implement OnActionExecuting and OnActionExecuted methods. For more information about this, see this MSDN article "Filtering in ASP.NET MVC".

Then bind the filter in the 'RegisterService' method inside 'NinjectMVC3.cs' file using 'BindFilter' method. Here we specify where the filter will apply (which controller or action or a combination of both). I'm assuming that you have used NuGet to implement Ninject in your project. That way, you will have 'NinjectMVC3.cs' file generated for you.

If you haven't implemented Ninject on your project, you can see my post "Building MVC Application using Dependency Injection with MVCScaffolding and Ninject" on how to install it.

Let's go through an example. In this example, we are going to implement a simple action filter that returns a greeting message through ViewData.
First, we create a simple service interface and an implementation class.
public interface IGreetingService
{
    string GetGreeting();
}

public class GreetingServiceImplOne : IGreetingService
{
    public string GetGreeting()
    {
        return "Greeting..";
    }
}

Then we create our filter class. Because it is an action filter, it will use IActionFilter to be inherited from.
public class GreetingFilter : IActionFilter
{
    private readonly IGreetingService _service;

    public GreetingFilter(IGreetingService greetingService)
    {
        this._service = greetingService;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        //at the moment this is empty because we don't need this method
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        ViewResult result = filterContext.Result as ViewResult;
        if (result != null)
        {
            result.ViewData["Greeting"] += _service.GetGreeting();
        }
    }
}
Notice the class includes a private service member and a constructor to inject a service object to itself.

Lastly, we need to bind the filter inside 'RegisterServices' method in 'NinjectMVC3.cs' class. In this case we state that the filter would only apply to the Index action of PlayersController.
private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IGreetingService>().To<GreetingServiceImplOne>();

    //filter is applied to Index action of PlayersController
    kernel.BindFilter<GreetingFilter>(FilterScope.Action, 0)
        .When((controllerContext, actionDescriptor) =>
            actionDescriptor.ControllerDescriptor.ControllerType == typeof(PlayersController) &&
            actionDescriptor.ActionName == "Index");
            
    //this one here is similar as the one above            
    /*kernel.BindFilter<GreetingFilter>(FilterScope.Action, 0)
        .When((controllerContext, actionDescriptor) => 
            actionDescriptor.ControllerDescriptor.ControllerName == "Players" &&
            actionDescriptor.ActionName == "Index");*/

    //this is another example where the filter to be applied to all actions of MatchesController
    //kernel.BindFilter<GreetingFilter>(FilterScope.Action, 0).WhenControllerType<MatchesController>();
}        
Notice that, first we bind the service interface with its implementation class. Then we configure the filter using 'BindFilter' method to particular controller(s) or action(s). The commented codes show some more configuration examples.

To see more possible filter configuration see these official Ninject MVC documentation for filters; https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations and https://github.com/ninject/ninject.web.mvc/wiki/Conditional-bindings-for-filters

To test our work, just try to render our ViewData on some view pages. In my case I use a shared page for all of my views which is '_Layout.cshtml' (under 'Views/Shared' folder), so I just need to render the ViewData there and test the result.

Easy..

No comments: