Search code examples
c#asp.net-coresimple-injector

Inject Logger in IExceptionFilter with SimpleInjector


I'm using Simple Injector in an ASP.NET Core 2.1 WebAPI application. I have an IExceptionFilter in which I want to log the error. My code is below (working), but is this the correct way? It looks like lot of work

public class HttpGlobalExceptionFilter : IExceptionFilter
{
    private readonly ILogger logger;

    public HttpGlobalExceptionFilter(Infrastructure.Common.ILogger logger)
    {
        // This is my own logger, implemented along the lines as mentioned here:
        // https://stackoverflow.com/questions/41243485/
        this.logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        // Irrelevant stuff
        // ...

        var error = $"An error occured: {context.Exception.Message}";
        this.logger.LogError(error);
        context.ExceptionHandled = true;
    }
}

In my Startup.cs ConfigureServices(), I have:

services.AddMvc(options =>
{
    options.Filters.Add(new SimpleInjectorExceptionFilterDispatcher(
        this.container.GetInstance<IExceptionFilter>));
    // ...
});

instead of

services.AddMvc(options =>
{
    options.Filters.Add<HttpGlobalExceptionFilter>();
    // ...
});

In my bindings:

container.Register<IExceptionFilter, HttpGlobalExceptionFilter>();

Finally, the dispatcher:

public sealed class SimpleInjectorExceptionFilterDispatcher : IExceptionFilter
{
    private readonly Func<IExceptionFilter> exceptionFilterFunc;

    public SimpleInjectorExceptionFilterDispatcher(
        Func<IExceptionFilter> exceptionFilterFunc)
    {
        this.exceptionFilterFunc = exceptionFilterFunc;
    }

    public void OnException(ExceptionContext context)
    {
        this.exceptionFilterFunc.Invoke().OnException(context);
    }
}

Is this the way to go about getting Simple Injector work with different components of ASP.NET? This is complex setup, and I would prefer to have something simpler so that all developers can easily understand the code.

Also, can I totally avoid Microsoft DI and use Simple Injector (and expect simpler configurations?)

References:

Simple Injector inject dependency into custom global authentication filters and OWIN middle ware OAuthAuthorizationServerProvider

Simple Injector: Register ILogger<T> by using ILoggerFactory.CreateLogger<T>()


Solution

  • In case HttpGlobalExceptionFilter's dependencies (currently ILogger) are singletons, you could simplify your solution by cutting out the SimpleInjectorExceptionFilterDispatcher. This would reduce the solution to the following:

    options.Filters.Add(new HttpGlobalExceptionFilter(new LoggerAdapter()));
    

    This constructs the filter and its dependencies manually. You can also resolve it from the Container. To make sure that you don't accidentally create Captive Dependencies, you should make sure that you register HttpGlobalExceptionFilter as Singleton:

    container.RegisterSingleton<HttpGlobalExceptionFilter>();
    
    services.AddMvc(options =>
    {
        options.Filters.Add(this.container.GetInstance<HttpGlobalExceptionFilter>());
    });
    

    Instead of using MVC filters, you can also go one level up and define a middleware component and hook it up as described here in the Simple Injector documentation. This simplifies the registration even further, although it might complicate your implementation, depending on your exact needs.

    This means rewriting your HttpGlobalExceptionFilter to a middleware component. This could look something as follows:

    public sealed class HttpGlobalExceptionMiddleware
        : Microsoft.AspNetCore.Http.IMiddleware
    {
        private readonly Infrastructure.Common.ILogger logger;
    
        public HttpGlobalExceptionMiddleware(ILogger logger) =>
            this.logger = logger;
    
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            try
            {
                await next(context);
            }
            catch (Exception ex)
            {
                var error = $"An error occured: {ex.Message}";
                this.logger.LogError(error);            
                throw;
            }
        }
    }
    

    Using Simple Injector's UseMiddleware extension method, you can register it Simple Injector and at the same time add it to ASP.NET Core's middleware pipeline:

    app.UseMiddleware<HttpGlobalExceptionMiddleware>(container);