.NET API中如何为无效枚举值返回更友好的错误信息
.NET API 枚举字段友好错误提示方案(兼顾Swagger展示)
要同时保留枚举类型在Swagger中的可选值展示优势,又能输出直观的错误提示,以下是几种低成本实现方案:
方案一:自定义JSON转换器(推荐)
通过自定义JSON转换逻辑,在枚举值转换失败时抛出明确的自定义异常,再通过全局异常处理统一格式化错误响应。
1. 实现自定义枚举转换器与异常类
// 自定义枚举转换器 public class FriendlyEnumConverter<T> : JsonConverter<T> where T : struct, Enum { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { string value = reader.GetString()!; if (Enum.TryParse<T>(value, ignoreCase: true, out var result)) { return result; } // 抛出携带无效值的自定义异常 throw new InvalidEnumValueException(value, typeof(T)); } if (reader.TokenType == JsonTokenType.Number) { int numValue = reader.GetInt32(); if (Enum.IsDefined(typeof(T), numValue)) { return (T)Enum.ToObject(typeof(T), numValue); } throw new InvalidEnumValueException(numValue.ToString(), typeof(T)); } throw new InvalidEnumValueException(reader.TokenType.ToString(), typeof(T)); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); } } // 自定义枚举异常类 public class InvalidEnumValueException : Exception { public InvalidEnumValueException(string invalidValue, Type enumType) : base($"value [{invalidValue}] is invalid.") { } }
2. 注册转换器
在Program.cs中全局注册转换器,或直接标记在枚举类型上:
// 全局注册 builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new FriendlyEnumConverter<你的枚举类型>()); }); // 或直接标记枚举 [JsonConverter(typeof(FriendlyEnumConverter<你的枚举类型>))] public enum EnumType { Option1, Option2 }
3. 全局异常处理中间件
捕获自定义异常并格式化响应:
public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; public ExceptionHandlingMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (InvalidEnumValueException ex) { context.Response.StatusCode = StatusCodes.Status400BadRequest; context.Response.ContentType = "application/json"; var problemDetails = new ProblemDetails { Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1", Title = "One or more validation errors occurred.", Status = StatusCodes.Status400BadRequest, Errors = new Dictionary<string, string[]> { { "Value", new[] { ex.Message } } } }; await context.Response.WriteAsJsonAsync(problemDetails); } } } // 注册中间件(在UseRouting之后,UseEndpoints之前) app.UseMiddleware<ExceptionHandlingMiddleware>();
方案二:自定义验证属性
利用.NET内置验证系统,为枚举字段添加自定义验证规则,适合处理数字类型的无效枚举值:
1. 实现验证属性
public class ValidEnumValueAttribute : ValidationAttribute { private readonly Type _enumType; public ValidEnumValueAttribute(Type enumType) { if (!enumType.IsEnum) throw new ArgumentException("类型必须是枚举"); _enumType = enumType; } protected override ValidationResult IsValid(object? value, ValidationContext validationContext) { if (value == null) return ValidationResult.Success; if (Enum.IsDefined(_enumType, value)) return ValidationResult.Success; return new ValidationResult($"value [{value}] is invalid."); } }
2. 在DTO中使用
public class RequestDto { [ValidEnumValue(typeof(EnumType))] public EnumType? Type { get; set; } }
方案三:自定义ProblemDetailsFactory
重写框架默认的错误信息生成逻辑,直接修改枚举转换错误的输出格式,但需注意框架错误信息可能随版本变化:
public class CustomProblemDetailsFactory : ProblemDetailsFactory { private readonly ProblemDetailsFactory _defaultFactory; public CustomProblemDetailsFactory(ProblemDetailsFactory defaultFactory) { _defaultFactory = defaultFactory; } public override ValidationProblemDetails CreateValidationProblemDetails( HttpContext httpContext, ModelStateDictionary modelStateDictionary, int? statusCode = null, string? title = null, string? type = null, string? detail = null, string? instance = null) { var problemDetails = _defaultFactory.CreateValidationProblemDetails( httpContext, modelStateDictionary, statusCode, title, type, detail, instance); // 替换枚举转换错误信息 if (problemDetails.Errors != null) { var newErrors = new Dictionary<string, string[]>(); foreach (var kv in problemDetails.Errors) { bool isEnumError = kv.Value.Any(msg => msg.Contains("could not be converted to") && msg.Contains("Enum")); if (isEnumError) { // 提取无效值(可根据实际错误信息优化正则) var match = System.Text.RegularExpressions.Regex.Match(kv.Value[0], @"JSON value (.+) could not be converted"); string invalidValue = match.Success ? match.Groups[1].Value : "invalid"; newErrors["Value"] = new[] { $"value [{invalidValue}] is invalid." }; } else { newErrors.Add(kv.Key, kv.Value); } } problemDetails.Errors = newErrors; problemDetails.Extensions.Remove("traceId"); } return problemDetails; } // 实现CreateProblemDetails方法(逻辑类似) public override ProblemDetails CreateProblemDetails(HttpContext httpContext, int? statusCode = null, string? title = null, string? type = null, string? detail = null, string? instance = null) { var problemDetails = _defaultFactory.CreateProblemDetails(httpContext, statusCode, title, type, detail, instance); // 可选:处理非验证类的枚举错误 return problemDetails; } } // 注册自定义工厂 builder.Services.AddSingleton<ProblemDetailsFactory, CustomProblemDetailsFactory>();
方案对比
- 方案一:最稳定,能准确捕获所有枚举转换错误,错误信息精准,同时完全保留Swagger的枚举展示。
- 方案二:实现简单,但仅能处理验证阶段的错误,无法捕获JSON转换时的异常(如字符串转枚举失败)。
- 方案三:无需修改业务代码,但依赖框架错误信息格式,兼容性较差。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

