.NET5环境下Asp.Net Core API如何优雅实现集中式ProblemDetails校验错误处理
实现方案
方案1:重写InvalidModelStateResponseFactory(推荐)
ASP.NET Core 针对标记了[ApiController]特性的控制器默认会自动执行模型校验,你不需要在每个Action方法中手动判断ModelState.IsValid,只需要替换默认的无效模型响应工厂即可实现集中化处理。
在Startup.cs的ConfigureServices方法中添加如下配置:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { // 构造标准ProblemDetails结构 var problemDetails = new ProblemDetails { Type = "parameter_validation_failure", Title = "参数校验失败", Status = StatusCodes.Status400BadRequest, Detail = "请求提交的参数不符合预设校验规则", Instance = context.HttpContext.Request.Path }; // 构造自定义Invalid Fields扩展字段 var invalidFields = new List<object>(); foreach (var (fieldKey, fieldState) in context.ModelState) { if (!fieldState.Errors.Any()) continue; // 读取属性DisplayName var displayName = fieldKey; var parameter = context.ActionDescriptor.Parameters.FirstOrDefault(p => p.BindingInfo?.BinderModelName == fieldKey || p.Name == fieldKey); if (parameter != null) { var metadata = context.ModelMetadata.GetMetadataForType(parameter.ParameterType); var propertyMeta = metadata.Properties.FirstOrDefault(p => p.Name == fieldKey); if (propertyMeta != null) { displayName = propertyMeta.DisplayName ?? fieldKey; } } // 收集每个字段的错误信息、错误码 foreach (var error in fieldState.Errors) { string errorCode = "InvalidParameter"; // 读取自定义ValidationAttribute中存储的错误码 if (error.Exception is CustomValidationException customEx) { errorCode = customEx.ErrorCode; } invalidFields.Add(new { FieldName = fieldKey, DisplayName = displayName, ErrorMessage = error.ErrorMessage, ErrorCode = errorCode }); } } // 添加扩展字段 problemDetails.Extensions.Add("Invalid Fields", invalidFields); return new BadRequestObjectResult(problemDetails) { ContentTypes = { "application/problem+json" } }; }; });
方案2:自定义ValidationAttribute绑定错误码
如果需要将错误码和校验逻辑绑定,不需要在响应工厂中统一匹配,可以在自定义校验特性中直接存储错误码,示例如下:
public class CustomNotNullAttribute : ValidationAttribute { // 自定义错误码字段 public string ErrorCode { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value == null) { // 返回携带错误码的校验结果 return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName }) { CustomState = new { ErrorCode = ErrorCode } }; } return ValidationResult.Success; } }
在模型属性上使用时直接指定错误码:
public class UserCreateRequest { [CustomNotNull(ErrorMessage = "用户名不能为空", ErrorCode = "USER_001")] [DisplayName("用户名")] public string UserName { get; set; } }
在响应工厂中可以直接读取存储的错误码:
if (fieldState.Errors.FirstOrDefault()?.CustomState is dynamic state) { errorCode = state.ErrorCode; }
方案3:自定义全局Action过滤器
如果需要更灵活的扩展逻辑(比如校验失败日志上报),可以实现全局Action过滤器:
- 先关闭默认的模型校验过滤器:
services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = true; });
- 实现自定义校验过滤器:
public class GlobalModelValidateFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { // 此处构造ProblemDetails的逻辑和方案1完全一致 // 最终将context.Result赋值为构造好的BadRequestObjectResult即可 } } public void OnActionExecuted(ActionExecutedContext context) { } }
- 注册全局过滤器:
services.AddControllers(options => { options.Filters.Add<GlobalModelValidateFilter>(); });
内容的提问来源于stack exchange,提问作者Vivek Nuna
相关产品推荐
相关产品推荐

