在 Web API 中处理异常的灵活方法

Flexible way to handle exception in Web API

提问人:Tiến Đạt Trương 提问时间:7/26/2023 最后编辑:Tiến Đạt Trương 更新时间:7/26/2023 访问量:58

问:

我目前正在使用 .NET 6 制作 Web API。 下面的代码是我的异常处理程序类:

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);
            }
        }
    }

在 Program.cs 类中:

app.UseMiddleware<ExceptionsMiddleware>();

这工作正常。但是,如果我想创建一个新的异常类型,我必须再次修改类,但是 SOLID 中的“O”原则不允许我这样做。ExceptionsMiddleware

因此,有没有其他解决方案? 先谢谢你。

C# asp.net 异常 错误处理

评论


答:

0赞 RJA 7/26/2023 #1

另一种解决方案是更早地处理预期的异常。控制者可以负责创建适当的响应(、、、等)。基于操作的结果,而不是在中间件中执行所有这些操作。然后,您可以使用异常处理中间件来处理意外异常。这是一个设计选择。Ok()NotFound()BadRequest()

0赞 EddieDemon 7/26/2023 #2

您可以做的是将 Exceptions 处理程序使用的 DI 容器中添加一个额外的服务。异常处理程序会将异常转发到子处理程序,子处理程序可以从泛型基添加。这就是我想出的。希望对你有所帮助!

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}\"}}"));
    }
}

编辑:原始不允许处理。System.Exception