You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:45:06