ASP.NET MVC 5 Ninject

Injecting MVC and HTTP attributes and filters using Ninject

The problem: null dependencies

Ninject has some fun quirks. For instance, one “bug” I came across was that if you hit an ASP.NET controller with a custom attribute, the attribute’s constructor-injected dependencies would be null on startup. For example, take the following ActionFilterAttribute:

public class ForceHttpAttribute : ActionFilterAttribute
{
    private readonly IControllerContextHelper _controllerContextHelper;

    public ForceHttpAttribute(IControllerContextHelper controllerContextHelper)
    {
        _controllerContextHelper = controllerContextHelper;
    }

    /// <summary>
    /// Called before the controller action executes. Checks for HTTPS requsts
    /// and redirects to HTTP if so.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var currentUri = _controllerContextHelper.GetRequestUri(filterContext);

        var isSecure = _controllerContextHelper.IsSecureConnection(filterContext);

        if (isSecure)
        {
            var secureUri = currentUri.AbsoluteUri.Replace("https://", "http://");
            filterContext.Result = new RedirectResult(secureUri);
        }
    }
}

You can use BindFilter to bind this to an implementation or an interface, such as the following INCORRECT WAY (don’t do this!):

kernel.BindFilter<ForceHttpAttribute>(System.Web.Mvc.FilterScope.Controller, 0).WhenControllerHas<ForceHttpAttribute>();

This is wrong for a couple reasons. For instance, you’re binding an implementation to itself. This will lead to the attribute’s OnActionExecuting to run twice. So, don’t do this. For me, the first time OnActionExecuting ran, there were null dependencies (sometimes throwing null reference exceptions), but the second time around it would execute as expected. This lead to some fun debugging…

The solution

The solution I found was to refactor the above class into the following:

public class ForceHttpAttribute : Attribute
{
    public class Implementation : ActionFilterAttribute
    {
        private readonly IControllerContextHelper _controllerContextHelper;

        public Implementation(IControllerContextHelper controllerContextHelper)
        {
            _controllerContextHelper = controllerContextHelper;
        }

        /// <summary>
        /// Called before the controller action executes. Checks for HTTPS requsts
        /// and redirects to HTTP if so.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var currentUri = _controllerContextHelper.GetRequestUri(filterContext);

            var isSecure = _controllerContextHelper.IsSecureConnection(filterContext);

            if (isSecure)
            {
                var secureUri = currentUri.AbsoluteUri.Replace("https://", "http://");
                filterContext.Result = new RedirectResult(secureUri);
            }
        }
    }
}

With the following Ninject binding:

kernel.BindFilter<ForceHttpAttribute.Implementation>(System.Web.Mvc.FilterScope.Controller, 0)
     .WhenControllerHas<ForceHttpAttribute>()
     .InRequestScope();

So what is this telling Ninject? It says whenever you find the filter [ForceHttpAttribute] (an implementation of Attribute), use the nested class ForceHttpAttribute.Implementation (an implementation of ActionFilterAttribute).

This binding gave expected results. The dependencies were no longer null and the OnActionExecuting only ran once.

If you’re using HTTP filter’s, you will have to use the BindHttpFilter method:

kernel.BindHttpFilter<MyHttpAttribute.Implementation>(FilterScope.Controller)
     .WhenControllerHas<MyHttpAttribute>()
     .InRequestScope();

You can also pass the constructor arguments using the WithConstructorArgumentFromControllerAttribute method:

kernel.BindHttpFilter(FilterScope.Controller)
     .WhenControllerHas().InRequestScope()
     // "myParameter" is the constructor parameter name, MyParamater is the class property on MyHttpAttribute (i.e. public string MyParamater {get;set;})
     .WithConstructorArgumentFromControllerAttribute("myParameter", a => a.MyParameter);

Problems? Questions? Did I save you some time? Let me know on Twitter @kerryritter.

Leave a Reply

Your email address will not be published. Required fields are marked *