一、为什么错误处理是API的“生命线”?

在现代Web开发中,错误处理不仅是代码健壮性的保障,更是用户体验和系统安全的核心环节。

常见问题

  • 500 Internal Server Error:暴露系统细节,攻击者可借此挖掘漏洞
  • 未捕获的异常:导致服务崩溃或数据不一致
  • 混乱的错误响应:客户端无法快速定位问题根源

核心目标

  1. 统一错误格式(如ProblemDetails)
  2. 细粒度控制(按环境返回不同信息)
  3. 日志与监控集成(快速定位生产问题)

二、全局异常处理:中间件 vs 过滤器

1. 使用中间件捕获全局异常

适合场景:跨所有请求的通用错误处理(如HTTP管道异常、未处理的异常)

// Middleware/ExceptionHandlingMiddleware.cs
public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;

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

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context); // 执行后续中间件
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Global exception caught"); // 记录日志

            context.Response.ContentType = "application/problem+json";
            context.Response.StatusCode = 500;

            var problem = new ProblemDetails
            {
                Status = 500,
                Title = "Internal Server Error",
                Detail = "An unexpected error occurred. Please contact support.",
                Instance = context.Request.Path
            };

            // 开发环境可附加堆栈跟踪
            if (context.RequestServices.GetService<IWebHostEnvironment>()?.IsDevelopment() == true)
            {
                problem.Extensions["exception"] = ex.ToString();
            }

            await context.Response.WriteAsync(JsonSerializer.Serialize(problem));
        }
    }
}

// Program.cs 注册中间件
app.UseMiddleware<ExceptionHandlingMiddleware>();

2. 自定义异常过滤器

适合场景:针对控制器/操作的特定异常处理(如业务逻辑异常)

// Filters/CustomExceptionFilter.cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilter : IAsyncExceptionFilter
{
    private readonly ILogger<CustomExceptionFilter> _logger;

    public CustomExceptionFilter(ILogger<CustomExceptionFilter> logger)
    {
        _logger = logger;
    }

    public async Task OnExceptionAsync(ExceptionContext context)
    {
        _logger.LogError(context.Exception, "Exception in {Controller}/{Action}", 
            context.ActionDescriptor.RouteValues["controller"],
            context.ActionDescriptor.RouteValues["action"]);

        var problem = new ProblemDetails
        {
            Status = context.Exception switch
            {
                ArgumentException => 400,
                KeyNotFoundException => 404,
                SecurityException => 403,
                _ => 500
            },
            Title = context.Exception.Message,
            Instance = context.HttpContext.Request.Path
        };

        context.HttpContext.Response.ContentType = "application/problem+json";
        context.Result = new ObjectResult(problem);
        context.ExceptionHandled = true; // 关键:标记异常已处理
    }
}

// 注册过滤器(全局或局部)
services.AddControllers(options =>
{
    options.Filters.Add<CustomExceptionFilter>();
});

三、API错误响应标准化:ProblemDetails规范

1. 默认行为配置

// Program.cs
builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var problem = new ValidationProblemDetails(context.ModelState)
            {
                Status = 400,
                Title = "One or more validation errors occurred",
                Instance = context.HttpContext.Request.Path
            };
            return new BadRequestObjectResult(problem)
            {
                ContentTypes = { "application/problem+json" }
            };
        };
    });

2. 自定义ProblemDetails扩展

// Models/CustomProblemDetails.cs
public class CustomProblemDetails : ProblemDetails
{
    public string? CorrelationId { get; set; }
    public Dictionary<string, string[]>? AdditionalData { get; set; }
}

// 使用示例
var problem = new CustomProblemDetails
{
    Status = 404,
    Title = "Resource not found",
    CorrelationId = Guid.NewGuid().ToString(),
    AdditionalData = new Dictionary<string, string[]>
    {
        { "request_id", new[] { HttpContext.TraceIdentifier } }
    }
};

四、环境感知的错误信息控制

1. 根据环境返回不同详情

// Pages/Error.cshtml.cs
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }
    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var env = HttpContext.RequestServices.GetService<IWebHostEnvironment>();
        if (env?.IsDevelopment() == true)
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerFeature>();
            if (feature?.Error != null)
            {
                ExceptionMessage = feature.Error.ToString();
            }
        }
    }
}

2. 配置错误页面中间件

// Program.cs
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async context =>
{
    var env = context.HttpContext.RequestServices.GetService<IWebHostEnvironment>();
    var isDev = env?.IsDevelopment() == true;

    var message = isDev 
        ? $"Status Code: {context.HttpContext.Response.StatusCode}" 
        : "An error occurred.";

    await context.HttpContext.Response.WriteAsync(message);
});

五、生产级错误处理的最佳实践

1. 日志与监控集成

  • Serilog + Seq:结构化日志记录与查询
  • Application Insights:异常追踪与性能监控
// Program.cs
builder.Host.UseSerilog((ctx, lc) => lc
    .WriteTo.Console()
    .WriteTo.Seq("http://localhost:5341")
    .Enrich.FromLogContext()
    .ReadFrom.Configuration(ctx.Configuration));

2. 敏感信息过滤

  • 避免返回堆栈跟踪:生产环境禁用IncludeErrorDetailPolicy
  • 脱敏日志:过滤敏感字段(如密码、信用卡号)

3. 错误分类与重试机制

  • 瞬态错误(Transient):使用Polly实现重试策略
  • 永久性错误:记录并通知运维团队
// 使用Polly处理瞬态异常
var retryPolicy = Policy.Handle<HttpRequestException>()
    .Or<TimeoutException>()
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

六、示例:从注册到异常处理的全链路

1. 用户注册接口

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly UserManager<AppUser> _userManager;

    public UsersController(UserManager<AppUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpPost]
    public async Task<IActionResult> Register([FromBody] RegisterRequest request)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var user = new AppUser
        {
            UserName = request.Username,
            Email = request.Email
        };

        var result = await _userManager.CreateAsync(user, request.Password);
        if (!result.Succeeded)
        {
            return BadRequest(new
            {
                Error = "User creation failed",
                Details = result.Errors.Select(e => e.Description)
            });
        }

        return CreatedAtAction(nameof(Register), new { id = user.Id }, user);
    }
}

2. 自定义异常处理

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        try
        {
            var product = await _productService.GetProductAsync(id);
            if (product == null)
            {
                return NotFound(new { Error = "Product not found" });
            }
            return Ok(product);
        }
        catch (InvalidOperationException ex)
        {
            return Conflict(new { Error = ex.Message });
        }
    }
}

七、常见问题与解决方案

问题 原因 解决方案
500错误无堆栈跟踪 生产环境默认隐藏敏感信息 检查日志系统(如Serilog)
错误页面未显示 UseExceptionHandler未正确配置 确保中间件顺序:UseRouting之后
ProblemDetails格式错误 JSON序列化配置冲突 设置ContentTypesapplication/problem+json
过滤器未生效 未正确注册或优先级冲突 使用options.Filters.Add()全局注册

八、 构建生产级错误处理体系

技术点 核心价值 实施建议
中间件捕获全局异常 无遗漏的错误处理 优先处理HTTP管道异常
ProblemDetails标准 客户端友好,符合RFC规范 统一响应格式,避免碎片化
环境感知信息 开发环境调试,生产环境安全 使用IWebHostEnvironment区分环境
日志与监控集成 快速定位问题根源 部署Serilog + Seq或Application Insights
过滤器与中间件协同 细粒度控制与全局策略结合 中间件处理通用错误,过滤器处理业务逻辑错误
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐