BLOG
Injecting a dependency into an action filter using ninject
One of the most powerful features of using Inversion of Control/Dependency Injection is the ability to have highly testable code. When your dependencies are interfaces instead of concrete classes, it makes it very easy for a unit test to mock that dependency:
public class ProductsController : Controller
{
protected readonly IProductService productService;
public ProductsController(IProductService productService)
{
this.productService = productService;
}
[HttpPost]
public ActionResult GetProducts()
{
return Json(productService.GetAllProducts());
}
}
The above example shows an MVC controller that takes a single dependency: IProductService. There is a single action method that is using the service to get a list of products, which is being returned via JSON. Since the dependency is defined as an interface, the code is very easy to test with a mocking framework.
What happens when you want to apply the same concept to an Action Filter? Action Filters are attributes that are added to your controllers and actions at compile time so you can’t pass a dependency through the constructor. Well, with Ninject you can.
Using NuGet, install the Ninject.MVC3 package (also works in MVC 4). This will add both the Ninject and Ninject MVC packages and add a file called NinjectWebCommon.cs to the App_Start folder in your solution. This is where we will set up the dependency injection for our action filters. Before we can do that we will need to create our action filter.
For this example we will create a simple filter that could be used for logging. The first thing you need to do is create a dummy attribute that you can apply to your action methods:
public enum SeverityLevel
{
Warning,
Information
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class LogAttribute : FilterAttribute
{
public LogLevel Level { get; set; }
public LogAttribute(LogLevel level)
{
this.Level = level;
}
}
This can be applied to your action methods as follows:
[Log(SeverityLevel.Information)]
public Actionresult Index()
{
return View();
}
This attribute by itself does nothing, but it will be used as a hook to inject a dependency into our real filter:
public class LogFilter : IActionFilter
{
protected readonly ILogService logService;
protected readonly LogLevel level;
public LogFilter(ILogService logService, LogLevel level)
{
this.logService = logService;
this.level = level;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// implement logging logic here
this.logService.Log(...);
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// implement logging logic here
this.logService.Log(...);
}
}
You can now write unit tests against the filter class and mock up your dependency using a mocking framework, but there is one more thing we need to do to wire everything that is up. Right now our filter has no direct tie to our attribute. Let’s change that by adding the following code to the NinjectWebCommon class in the RegisterServices method:
private static void RegisterServices(IKernel kernel)
{
kernel.BindFilter(FilterScope.Action, 0)
.WhenActionMethodHas()
.WithConstructorArgumentFromActionAttribute("level", attr => attr.Level);
}
The BindFilter is is an extension method that exists in the Ninject.Web.Mvc.FilterBindingSyntax namespace. Once you add this line whenever Ninject creates an action method that has the LogAttribute applied to it, it will automatically apply the LogFilter and inject any dependencies that it knows how to create. That is all you need to get dependency injection working on an MVC action filter.