skip to Main Content

I’m currently making a web API with .NET 6.
The code bellow is my Exception handler class:

public class ExceptionsMiddleware
    {
        private readonly RequestDelegate requestDelegate;
        private readonly ILogger<ExceptionsMiddleware> logger;

        public ExceptionsMiddleware(RequestDelegate requestDelegate, ILogger<ExceptionsMiddleware> logger)
        {
            this.requestDelegate = requestDelegate;
            this.logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await requestDelegate(context);
            }
            catch (Exception ex)
            {
                var response = context.Response;
                response.ContentType = "application/json";
                var errorMessage = ex.Message;
                logger.LogError(ex, "Error occurred");
                switch (ex)
                {
                    case ApplicationException:
                        response.StatusCode = StatusCodes.Status400BadRequest;
                        break;
                    case KeyNotFoundException:
                    case ArgumentException:
                    case NullReferenceException:
                        response.StatusCode = StatusCodes.Status404NotFound;
                        break;
                    default:
                        response.StatusCode = StatusCodes.Status500InternalServerError;
                        errorMessage = "Error occurred";
                        break;
                }
                var result = System.Text.Json.JsonSerializer.Serialize(new { Message = errorMessage });
                await response.WriteAsync(result);
            }
        }
    }

And in Program.cs class:

app.UseMiddleware<ExceptionsMiddleware>();

This works fine. However, if I want to create a new exception type, I have to modify ExceptionsMiddleware class again, but the "O" principle in SOLID doesn’t allow me to do this.

Thus, is there any alternative solution for this?
Thank you in advance.

2

Answers


  1. An alternative solution is to handle expected exceptions earlier. The Controller can take the responsibility of creating an appropriate response (Ok(), NotFound(), BadRequest(), and so on. Based on the result of an action instead of doing all that in your Middleware. You can then use the exception handling middleware to handle unexpected exceptions. It’s a design choice.

    Login or Signup to reply.
  2. What you could do is add an extra service into the DI container which the Exceptions handler consumes. The exception handler will forward the exception to the sub-handler, which could be added from a generic base. This is what I came up with. Hope it helps!

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using System.Text;
    
    namespace Playground7;
    
    internal static partial class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder();
            // Add the handlers to the container.
            // Made them transient since we expect nothing to happen, lowers memory load in the long run
            // but makes the handling come slower.
            // Please note how we add it as generic type "ExceptionHandler"
            builder.Services.AddTransient<ExceptionHandler, InvalidCastExceptionHandler>();
            builder.Services.AddTransient<ExceptionHandler, InvalidDataExceptionHandler>();
    
            var app = builder.Build();
            // Add the middleware to the pipeline.
            app.UseMiddleware<ExceptionsMiddleware>();
            // Throw the exception!
            app.MapGet("/", () => { throw new InvalidCastException(); });
            app.Run();
        }
    }
    // Closed module
    internal class ExceptionsMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ExceptionsMiddleware> _logger;
        private readonly IServiceProvider _services;
    
        public ExceptionsMiddleware(RequestDelegate requestDelegate, ILogger<ExceptionsMiddleware> logger, IServiceProvider services)
        {
            _next = requestDelegate;
            _logger = logger;
            _services = services;
        }
    
        public async Task Invoke(HttpContext context)
        {
            try
            {
                // Await the rest of the pipeline...
                await _next(context).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                // Create the type which we expect the handler to be.
                // and add some generic sweetness.
                var targetHandlerType = typeof(ExceptionHandler<>)
                    .MakeGenericType(ex.GetType());
    
                // Search for that specific handler with our sweetened type.
                var handler = _services
                    .GetServices<ExceptionHandler>() // See how we first get our base-base type.
                    .Where(h => targetHandlerType.IsAssignableFrom(h.GetType()))
                    .FirstOrDefault();
    
                // No handler found, you could do something generic here...?
                if (handler is null)
                    return;
                // Open using external handlers.
                handler.HandleBase(context, ex);
            }
        }
    }
    
    // Base class to add to the DI container.
    internal abstract class ExceptionHandler
    {
        public abstract void HandleBase(HttpContext context, Exception exception);
    }
    
    // Base class for the handlers.
    internal abstract class ExceptionHandler<TException> : ExceptionHandler where TException : Exception
    {
        protected abstract void Handle(HttpContext context, TException exception);
        public override void HandleBase(HttpContext context, Exception exception)
        {
            Handle(context, (TException)exception);
        }
    }
    
    // Handler for an InvalidCastException.
    internal sealed class InvalidCastExceptionHandler : ExceptionHandler<InvalidCastException>
    {
        protected override void Handle(HttpContext context, InvalidCastException exception)
        {
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = 500;
            context.Response.Body.Write(Encoding.UTF8.GetBytes($"{{"error":"{exception.Message}"}}"));
        }
    }
    
    // Handler for a InvalidDataException.
    internal sealed class InvalidDataExceptionHandler : ExceptionHandler<InvalidDataException>
    {
        protected override void Handle(HttpContext context, InvalidDataException exception)
        {
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = 500;
            context.Response.Body.Write(Encoding.UTF8.GetBytes($"{{"error":"{exception.Message}"}}"));
        }
    }
    internal sealed class ExceptionHandler2 : ExceptionHandler<Exception>
    {
        protected override void Handle(HttpContext context, Exception exception)
        {
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = 500;
            context.Response.Body.Write(Encoding.UTF8.GetBytes($"{{"error":"{exception.Message}"}}"));
        }
    }
    

    EDIT: Original didn’t allow System.Exception to be handled.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search