Ninject Property Injection in WebAPI custom ExceptionFilterAttribute Not Working
Asked Answered
C

2

2

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);
    }        
Corridor answered 6/2, 2014 at 21:12 Comment(4)
Did you Bind it? May we see your bindings?Cretic
@Cretic I revised the post above to include my bindings. Thanks for looking at this!Corridor
Just for clarity, dont need to pass in the kernel as constructor argument. Ninject automatically injects all IKernel` arguments for you.Cretic
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?Chlorine
C
1

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.

Cretic answered 7/2, 2014 at 18:43 Comment(4)
unfortunately this didn't work. The reference to the event logger was still null. I revised my original question with my updated bindings as well as pointing out that I pointed to the custom filter in NinjectWebCommon -> CreateKernel GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), new NinjectWebApiFilterProvider(kernel));Corridor
I reproduced your code exactly and make it work, something is wrong, somewhere that I can`t see :(. Your project is solely WebAPI?Cretic
Another thing: Dont replace the IFilterPRovider` in GlobalConfiguration. The DependencyResolver you provided you resolve that, as long as you have the bindings.Cretic
thanks for working through this. I found out the reason it wasn't working was because I had the ErrorHandler registered in my WebApiConfig.cs class like this... config.Filters.Add(new ErrorHandlingAttribute()) and not on the controller as an attribute. As soon as I added it as an attribute the event logger was instantiated properly. UGH! Ideally I'd like to register the error handling once in the config... but if it requires adding it as an attribute to each class I'll just do it that way. Thanks again!!Corridor
C
4

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.

Chlorine answered 15/7, 2014 at 20:49 Comment(1)
+1 This is great. Thanks - It might be my configuration but this seems to interfere with my IPrincipal being null. Possibly because HttpContext.Current.User is null at this point in time?Hod
C
1

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.

Cretic answered 7/2, 2014 at 18:43 Comment(4)
unfortunately this didn't work. The reference to the event logger was still null. I revised my original question with my updated bindings as well as pointing out that I pointed to the custom filter in NinjectWebCommon -> CreateKernel GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), new NinjectWebApiFilterProvider(kernel));Corridor
I reproduced your code exactly and make it work, something is wrong, somewhere that I can`t see :(. Your project is solely WebAPI?Cretic
Another thing: Dont replace the IFilterPRovider` in GlobalConfiguration. The DependencyResolver you provided you resolve that, as long as you have the bindings.Cretic
thanks for working through this. I found out the reason it wasn't working was because I had the ErrorHandler registered in my WebApiConfig.cs class like this... config.Filters.Add(new ErrorHandlingAttribute()) and not on the controller as an attribute. As soon as I added it as an attribute the event logger was instantiated properly. UGH! Ideally I'd like to register the error handling once in the config... but if it requires adding it as an attribute to each class I'll just do it that way. Thanks again!!Corridor

© 2022 - 2024 — McMap. All rights reserved.