2
votes

I am trying to use Ninject to inject an EventLogger instance into a custom ExceptionFilterAttribute. Whenever I run the code, the EventLogger instance is null. I have implemented an IFilterProvider to resolve dependencies in a similar manner for my custom AuthorizationFilterAttribute, and that works fine. Any ideas?

Not Working

public class ErrorHandlingAttribute : ExceptionFilterAttribute
{
    [Inject]
    public IEventLogger EventLogger { get; set; }

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
       EventLogger.LogException(actionExecutedContext.Exception);

        actionExecutedContext.Response = actionExecutedContext.Request.
            CreateResponse(HttpStatusCode.BadRequest, 
            new ServiceErrorResponseDTO("An unhandled exception occurred while calling " + 
                actionExecutedContext.Request.RequestUri.ToString() + 
                ". This event has been logged. If you continue to receive this error contact Weichert"));
    }
}

Working

public class RequireAuthorizationAttribute : AuthorizationFilterAttribute
{
    [Inject]
    public IServiceRepository ServiceRepository { get; set; }

    public override void OnAuthorization(HttpActionContext actionContext)
    {


        #region Header Authentication

        var authHeader = actionContext.Request.Headers.Authorization;
        if (authHeader != null)
        {

Custom IFilterProvider

public class NinjectWebApiFilterProvider : IFilterProvider
{
    private IKernel _kernel;

    public NinjectWebApiFilterProvider(IKernel kernel)
    {
        _kernel = kernel;
    }

    public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
    {
        var controllerFilters = actionDescriptor.ControllerDescriptor.GetFilters().Select(instance => new FilterInfo(instance, FilterScope.Controller));
        var actionFilters = actionDescriptor.GetFilters().Select(instance => new FilterInfo(instance, FilterScope.Action));

        var filters = controllerFilters.Concat(actionFilters);

        foreach(var filter in filters)
        {
            _kernel.Inject(filter.Instance);
        }

        return filters;
    }
}

NinjectWebCommon CreateKernel Method

private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
        kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

        // Ad Ninject support for Web API.
        GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);

        GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), 
            new NinjectWebApiFilterProvider(kernel));

        RegisterServices(kernel);
        return kernel;
    }

NinjectWebCommon Bindings

private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<ServiceDbContext>().To<ServiceDbContext>();
        kernel.Bind<IServiceRepository>().To<ServiceRepository>();

        kernel.Bind<CareerDevelopmentDbContext>().To<CareerDevelopmentDbContext>();
        kernel.Bind<ICareerDevelopmentRepository>().To<CareerDevelopmentRepository>();
        kernel.Bind<ICareerDevelopmentService>().To<CareerDevelopmentService>();

        kernel.Bind<IEventLogger>().To<ServiceEventLogger>();
        kernel.Bind<IFilterProvider>().To<NinjectWebApiFilterProvider>().WithConstructorArgument("kernel", kernel);
    }        
2
Did you Bind it? May we see your bindings? - cvbarros
@cvbarros I revised the post above to include my bindings. Thanks for looking at this! - nerdn
Just for clarity, dont need to pass in the kernel as constructor argument. Ninject automatically injects all IKernel` arguments for you. - cvbarros
I have the exact same problem I've been working on and produced almost exactly the same code with exactly the same result. It won't bind to my custom ExceptionFilterAttribute. I see you also had the same problem that you were adding to to config.Filters.Add, but surely there's a better way to handle this than having to add the attribute to every controller? - Matt Burland

2 Answers

4
votes

I had the same problem and was configuring my error handler the same way by adding it to the filter collection in WebApiConfig.cs which meant it wasn't getting handled by the FilterProvider implementation I had added. So I did this instead:

public class LoggingExceptionFilterAttribute : ExceptionFilterAttribute, IExceptionFilter
{
    // this is what I wanted injected
    private IEmailService emailService;

    public LoggingExceptionFilterAttribute(IEmailService service)
    {
        emailService = service;
    }

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        // my implementation here
    }
}

Then I registered this in NinjectWebCommon like so:

kernel.Bind<System.Web.Http.Filters.IExceptionFilter>().To<LoggingExceptionFilterAttribute>().InSingletonScope();

And then in WebApiConfig I realized that I could get a hold of the DependencyResolver so I did this:

config.Filters.Add((IFilter)config.DependencyResolver.GetService(typeof(IExceptionFilter)));

Now Ninject handles constructing my exception filter and I can even do constructor injection instead of needing [Inject] attributes and I don't have to add my ExceptionFilterAttribute to every API controller.

1
votes

Ok, you have to make sure you are binding your custom IFilterProvider as well. As of writing the Ninject.Web.WebApi Nuget package is unstable and would do that automatically for you, if you were using it. Just in the same fashion Ninject.MVC3 does this for your regular controllers.

Just make sure you have this binding, and the replaced DependencyResolver will look for IFilterProvider implementation via your Ninject kernel as well:

kernel.Bind<IFilterProvider>().To<NinjectWebApiFilterProvider>();

Then your NinjectWebApiFilterProvider will kick in and inject dependencies into your filters as per your code.