ASP.NET Core 8.0 Web API枚举参数自动校验主体及日志自定义
回答
1. 自动校验的实现主体
这个枚举参数的自动校验是ASP.NET Core内置的模型绑定与验证系统完成的。
在控制器方法执行前,框架会先对路由、请求中的参数做模型绑定:当尝试将字符串参数转换为枚举类型时,如果找不到匹配的枚举值,会自动生成验证错误并标记ModelState为无效。此时框架会直接返回400响应,不会进入控制器方法执行——这个逻辑属于请求处理的早期阶段,和UseStatusCodePages、UseExceptionHandler中间件无关,所以即使注释掉这两个中间件,校验逻辑依然会生效。
2. 自定义逻辑上报至DataDog
可以通过全局动作过滤器或中间件两种方式捕获这类模型验证错误,再上报到DataDog,以下是具体实现方案:
方案一:使用全局动作过滤器(推荐)
过滤器能直接在模型验证阶段捕获错误,不需要解析响应体,更高效:
先通过NuGet安装DataDog的.NET客户端包:
Datadog.Trace、Datadog.Trace.AspNetCore创建自定义过滤器类:
public class EnumValidationLoggingFilter : ActionFilterAttribute { private readonly IDogStatsd _dogStatsd; // 通过依赖注入获取DataDog的Statsd客户端 public EnumValidationLoggingFilter(IDogStatsd dogStatsd) { _dogStatsd = dogStatsd; } public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { // 提取枚举参数的验证错误 var enumErrors = context.ModelState .Where(kv => kv.Value.Errors.Any(e => e.ErrorMessage.Contains("is not valid"))) .SelectMany(kv => kv.Value.Errors.Select(e => new { Parameter = kv.Key, InvalidValue = context.HttpContext.Request.RouteValues[kv.Key]?.ToString(), Message = e.ErrorMessage })) .ToList(); if (enumErrors.Any()) { // 上报计数指标到DataDog _dogStatsd.Increment("api.invalid_enum_requests", tags: new[] { $"endpoint:{context.HttpContext.Request.Path}", $"parameter:{enumErrors.First().Parameter}", $"invalid_value:{enumErrors.First().InvalidValue}" }); // 上报详细日志到DataDog foreach (var error in enumErrors) { _dogStatsd.LogError($"无效枚举参数: {error.Parameter} = {error.InvalidValue}, 错误信息: {error.Message}", tags: new[] { $"trace_id:{context.HttpContext.TraceIdentifier}" }); } } } base.OnActionExecuting(context); } }
- 在
Program.cs中注册全局过滤器:
builder.Services.AddControllers(options => { // 添加自定义过滤器 options.Filters.Add<EnumValidationLoggingFilter>(); }); // 初始化DataDog跟踪与日志 builder.Services.AddDatadogTrace(); builder.Services.AddDatadogLogging();
方案二:使用中间件捕获400响应
如果需要统一处理所有400错误,也可以用中间件捕获响应并上报:
- 在
Program.cs中添加中间件(注意要放在UseControllers之前):
// 启用响应体重读,以便读取响应内容 app.Use(async (context, next) => { var originalBody = context.Response.Body; using var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; await next(); // 重置流位置,以便读取内容 memoryStream.Seek(0, SeekOrigin.Begin); var responseBody = await new StreamReader(memoryStream).ReadToEndAsync(); memoryStream.Seek(0, SeekOrigin.Begin); // 复制回原响应体 await memoryStream.CopyToAsync(originalBody); context.Response.Body = originalBody; // 检测是否为模型验证错误的400响应 if (context.Response.StatusCode == StatusCodes.Status400BadRequest && responseBody.Contains("One or more validation errors occurred.")) { var dogStatsd = context.RequestServices.GetRequiredService<IDogStatsd>(); var invalidSource = context.Request.RouteValues["source"]?.ToString(); // 上报到DataDog dogStatsd.Increment("api.invalid_enum_requests", tags: new[] { $"endpoint:{context.Request.Path}", $"invalid_source:{invalidSource ?? "unknown"}" }); } }); app.UseControllers();
两种方案中,过滤器更精准针对模型验证阶段的错误,中间件则适合统一处理所有400响应场景,可根据需求选择。
内容的提问来源于stack exchange,提问作者Bohn
相关产品推荐
相关产品推荐

