符合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
相关产品推荐
相关产品推荐

