You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

符合SOLID开闭原则的.NET 6 Web API异常处理优化方案咨询

问题描述

我正在使用.NET 6开发Web API,当前自定义了如下ExceptionsMiddleware异常处理中间件:

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中的开闭原则,特此咨询替代解决方案。

替代解决方案

方案一:基于配置的异常-状态码映射

把异常与状态码的映射逻辑从中间件中抽离,通过依赖注入注册映射关系,新增异常时仅需添加配置,无需修改中间件代码。

1. 定义映射模型

public class ExceptionStatusCodeMapping
{
    public Type ExceptionType { get; set; }
    public int StatusCode { get; set; }
    public string ErrorMessageTemplate { get; set; } = "{Message}";
}

2. 注册映射配置

在Program.cs中注册异常与状态码的映射字典:

builder.Services.AddSingleton<IReadOnlyDictionary<Type, ExceptionStatusCodeMapping>>(new Dictionary<Type, ExceptionStatusCodeMapping>
{
    { typeof(ApplicationException), new ExceptionStatusCodeMapping { StatusCode = StatusCodes.Status400BadRequest } },
    { typeof(KeyNotFoundException), new ExceptionStatusCodeMapping { StatusCode = StatusCodes.Status404NotFound } },
    { typeof(ArgumentException), new ExceptionStatusCodeMapping { StatusCode = StatusCodes.Status404NotFound } },
    { typeof(NullReferenceException), new ExceptionStatusCodeMapping { StatusCode = StatusCodes.Status404NotFound } }
});

3. 修改中间件逻辑

让中间件注入映射配置,动态匹配异常处理规则:

public class ExceptionsMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionsMiddleware> _logger;
    private readonly IReadOnlyDictionary<Type, ExceptionStatusCodeMapping> _exceptionMappings;
    private readonly ExceptionStatusCodeMapping _defaultMapping = new() 
    { 
        StatusCode = StatusCodes.Status500InternalServerError, 
        ErrorMessageTemplate = "Error occurred" 
    };

    public ExceptionsMiddleware(RequestDelegate next, ILogger<ExceptionsMiddleware> logger, 
        IReadOnlyDictionary<Type, ExceptionStatusCodeMapping> exceptionMappings)
    {
        _next = next;
        _logger = logger;
        _exceptionMappings = exceptionMappings;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error occurred");
            var mapping = GetMatchingMapping(ex.GetType());
            var response = context.Response;
            
            response.ContentType = "application/json";
            response.StatusCode = mapping.StatusCode;
            
            var errorMessage = mapping.ErrorMessageTemplate.Replace("{Message}", ex.Message);
            var result = System.Text.Json.JsonSerializer.Serialize(new { Message = errorMessage });
            await response.WriteAsync(result);
        }
    }

    private ExceptionStatusCodeMapping GetMatchingMapping(Type exceptionType)
    {
        // 支持匹配继承的异常类型(如自定义异常继承ApplicationException)
        return _exceptionMappings.FirstOrDefault(kv => kv.Key.IsAssignableFrom(exceptionType)).Value 
               ?? _defaultMapping;
    }
}

优势:无需修改中间件即可新增/调整异常处理规则,支持系统自带异常和自定义异常,还能通过模板自定义错误消息。


方案二:自定义异常基类携带状态码

定义一个包含状态码属性的自定义异常基类,让所有业务异常继承该类,中间件直接根据基类属性处理响应。

1. 定义自定义异常基类

public class ApiException : Exception
{
    public int StatusCode { get; }

    public ApiException(string message, int statusCode) : base(message)
    {
        StatusCode = statusCode;
    }
}

// 示例业务异常
public class ResourceNotFoundException : ApiException
{
    public ResourceNotFoundException(string message) : base(message, StatusCodes.Status404NotFound)
    {
    }
}

public class BadRequestException : ApiException
{
    public BadRequestException(string message) : base(message, StatusCodes.Status400BadRequest)
    {
    }
}

2. 简化中间件逻辑

中间件优先处理ApiException类型,其他异常走默认500错误:

public class ExceptionsMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionsMiddleware> _logger;

    public ExceptionsMiddleware(RequestDelegate next, ILogger<ExceptionsMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (ApiException ex)
        {
            _logger.LogError(ex, "API业务错误");
            var response = context.Response;
            
            response.ContentType = "application/json";
            response.StatusCode = ex.StatusCode;
            
            var result = System.Text.Json.JsonSerializer.Serialize(new { Message = ex.Message });
            await response.WriteAsync(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "未处理的系统错误");
            var response = context.Response;
            
            response.ContentType = "application/json";
            response.StatusCode = StatusCodes.Status500InternalServerError;
            
            var result = System.Text.Json.JsonSerializer.Serialize(new { Message = "Error occurred" });
            await response.WriteAsync(result);
        }
    }
}

优势:业务异常语义清晰,异常本身携带处理信息,新增业务异常仅需创建子类,完全符合开闭原则,适合业务场景丰富的项目。


方案选择建议

  • 若需要处理大量系统自带异常,优先选择方案一;
  • 若以自定义业务异常为主,优先选择方案二;
  • 两种方案可结合使用:用方案一处理系统异常,方案二处理业务异常。

内容的提问来源于stack exchange,提问作者Dat Truong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:53:17