ASP.NET Core Web API字符串枚举绑定失败的全局处理方案咨询
在ASP.NET Core Web API中全局捕获枚举字符串反序列化绑定错误
要解决你遇到的这个问题——无需修改NuGet包中的模型,就能全局捕获枚举字符串反序列化的绑定错误并返回详细的400响应,我们可以通过自定义枚举转换器+自定义输入格式化器的组合来实现,全程只需要在API项目中做全局配置即可。
步骤1:自定义枚举转换器,捕获无效枚举值异常
首先,我们需要重写StringEnumConverter,在反序列化失败时抛出包含详细信息的异常(比如无效值、枚举类型、合法值列表),这样后续才能提取这些信息返回给客户端:
public class CustomStringEnumConverter : StringEnumConverter { public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { try { return base.Read(ref reader, typeToConvert, options); } catch (JsonException ex) { // 只处理枚举类型的反序列化失败 if (typeToConvert.IsEnum && reader.TokenType == JsonTokenType.String) { string invalidValue = reader.GetString()!; var validValues = string.Join(", ", Enum.GetNames(typeToConvert)); throw new JsonException( $"无效值 '{invalidValue}',枚举类型 '{typeToConvert.Name}' 的合法值为:{validValues}", ex); } throw; } } }
步骤2:自定义输入格式化器,将异常转化为ModelState错误
默认情况下,反序列化异常会导致整个模型变为null,且ModelState中只有模糊的错误信息。我们需要自定义输入格式化器,捕获这个异常,解析出对应的属性名,把错误添加到ModelState的具体属性中:
public class CustomSystemTextJsonInputFormatter : SystemTextJsonInputFormatter { public CustomSystemTextJsonInputFormatter(JsonOptions options) : base(options.JsonSerializerOptions) { } public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { try { return await base.ReadRequestBodyAsync(context, encoding); } catch (JsonException ex) { // 从异常的Path中提取属性名(比如"$.sex" -> "sex") string? propertyName = ex.Path?.TrimStart('$', '.'); string errorMessage = ex.Message; // 将错误绑定到对应的属性,没有属性名则添加为全局错误 if (!string.IsNullOrEmpty(propertyName)) { context.ModelState.TryAddModelError(propertyName, errorMessage); } else { context.ModelState.TryAddModelError(string.Empty, errorMessage); } return InputFormatterResult.Failure(); } } }
步骤3:全局注册自定义组件并配置API行为
在Program.cs中,我们需要替换默认的输入格式化器,注册自定义枚举转换器,并配置API返回标准化的验证错误响应:
var builder = WebApplication.CreateBuilder(args); // 添加控制器服务,替换输入格式化器 builder.Services.AddControllers(options => { // 移除默认的SystemTextJson输入格式化器,添加自定义版本 var defaultFormatter = options.InputFormatters.OfType<SystemTextJsonInputFormatter>().FirstOrDefault(); if (defaultFormatter != null) { options.InputFormatters.Remove(defaultFormatter); options.InputFormatters.Add(new CustomSystemTextJsonInputFormatter(options.JsonOptions)); } }) .AddJsonOptions(options => { // 注册自定义枚举转换器,保持大小写不敏感匹配 options.JsonSerializerOptions.Converters.Add(new CustomStringEnumConverter()); options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; }); // 配置API行为,返回标准化的400验证错误 builder.Services.Configure<ApiBehaviorOptions>(options => { options.InvalidModelStateResponseFactory = context => { var problemDetails = new ValidationProblemDetails(context.ModelState) { Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1", Status = StatusCodes.Status400BadRequest, Title = "存在验证错误", Detail = "请查看errors字段获取详细信息", Instance = context.HttpContext.Request.Path }; return new BadRequestObjectResult(problemDetails) { ContentTypes = { "application/problem+json" } }; }; }); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
效果演示
当客户端提交包含无效枚举值的请求:
{ "name": "Ann", "sex": "femal" }
API会返回400 Bad Request,响应体包含详细的错误信息:
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "存在验证错误", "status": 400, "detail": "请查看errors字段获取详细信息", "instance": "/api/people", "errors": { "sex": [ "无效值 'femal',枚举类型 'SexEnum' 的合法值为:Male, Female, Other" ] } }
这样就完全满足了你的需求:全局配置生效,无需修改NuGet包中的模型,错误信息清晰,且不会进入控制器方法处理。
内容的提问来源于stack exchange,提问作者Viktor
相关产品推荐
相关产品推荐

