.NET Core错误处理全面解析:从全局捕获到生产级优化
API错误处理是现代Web开发的关键环节,确保系统健壮性、安全性和用户体验。文章探讨了全局异常处理的两种方式(中间件适合通用错误捕获,过滤器适合业务逻辑异常),并推荐使用ProblemDetails规范实现标准化错误响应。同时强调环境感知控制,开发环境暴露详细错误,生产环境隐藏敏感信息。通过日志集成和统一错误格式,帮助开发者快速定位问题,提升系统可靠性。
·
一、为什么错误处理是API的“生命线”?
在现代Web开发中,错误处理不仅是代码健壮性的保障,更是用户体验和系统安全的核心环节。
常见问题
- 500 Internal Server Error:暴露系统细节,攻击者可借此挖掘漏洞
- 未捕获的异常:导致服务崩溃或数据不一致
- 混乱的错误响应:客户端无法快速定位问题根源
核心目标
- 统一错误格式(如ProblemDetails)
- 细粒度控制(按环境返回不同信息)
- 日志与监控集成(快速定位生产问题)
二、全局异常处理:中间件 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序列化配置冲突 | 设置ContentTypes 为application/problem+json |
过滤器未生效 | 未正确注册或优先级冲突 | 使用options.Filters.Add() 全局注册 |
八、 构建生产级错误处理体系
技术点 | 核心价值 | 实施建议 |
---|---|---|
中间件捕获全局异常 | 无遗漏的错误处理 | 优先处理HTTP管道异常 |
ProblemDetails标准 | 客户端友好,符合RFC规范 | 统一响应格式,避免碎片化 |
环境感知信息 | 开发环境调试,生产环境安全 | 使用IWebHostEnvironment 区分环境 |
日志与监控集成 | 快速定位问题根源 | 部署Serilog + Seq或Application Insights |
过滤器与中间件协同 | 细粒度控制与全局策略结合 | 中间件处理通用错误,过滤器处理业务逻辑错误 |
更多推荐
所有评论(0)