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

.NET 6 Web API如何按属性注解顺序返回数据验证错误

.NET 6 Web API 按属性定义顺序返回验证错误信息

默认情况下,ASP.NET Core的模型验证错误会按属性名称的字典序返回,而非类中属性的定义顺序。要实现按属性定义顺序返回错误,可通过以下步骤自定义处理:

1. 自定义属性顺序元数据提供器

创建元数据提供器,记录类中属性的定义顺序,并绑定到模型元数据的Order字段:

public class PropertyOrderMetadataProvider : IMetadataDetailsProvider, IValidationMetadataProvider
{
    private static readonly Dictionary<Type, List<string>> _propertyOrderCache = new();

    public void CreateValidationMetadata(ValidationMetadataProviderContext context)
    {
        var type = context.Key.ContainerType ?? context.Key.ModelType;
        if (!_propertyOrderCache.ContainsKey(type))
        {
            _propertyOrderCache[type] = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Select(p => p.Name)
                .ToList();
        }

        var propertyOrder = _propertyOrderCache[type].IndexOf(context.Key.Name);
        context.ValidationMetadata.Order = propertyOrder;
    }
}

2. 注册元数据提供器

在Program.cs中注册上述提供器,让框架生成模型元数据时使用它:

builder.Services.AddControllers()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(new PropertyOrderMetadataProvider());
    });

3. 自定义验证问题细节工厂

重写默认的ProblemDetailsFactory,生成错误信息时按属性元数据的Order排序:

public class OrderedProblemDetailsFactory : ProblemDetailsFactory
{
    private readonly ApiBehaviorOptions _options;

    public OrderedProblemDetailsFactory(IOptions<ApiBehaviorOptions> options)
    {
        _options = options.Value;
    }

    public override ProblemDetails CreateProblemDetails(
        HttpContext httpContext,
        int? statusCode = null,
        string? title = null,
        string? type = null,
        string? detail = null,
        string? instance = null)
    {
        var problemDetails = new ProblemDetails
        {
            Status = statusCode,
            Title = title,
            Type = type,
            Detail = detail,
            Instance = instance
        };

        ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode ?? 500);
        return problemDetails;
    }

    public override ValidationProblemDetails CreateValidationProblemDetails(
        HttpContext httpContext,
        ModelStateDictionary modelStateDictionary,
        int? statusCode = null,
        string? title = null,
        string? type = null,
        string? detail = null,
        string? instance = null)
    {
        if (modelStateDictionary == null)
            throw new ArgumentNullException(nameof(modelStateDictionary));

        var problemDetails = new ValidationProblemDetails(modelStateDictionary)
        {
            Status = statusCode,
            Type = type,
            Detail = detail,
            Instance = instance
        };

        if (title != null)
        {
            problemDetails.Title = title;
        }

        ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode ?? 400);

        // 按属性元数据的Order排序错误条目
        var orderedErrors = problemDetails.Errors
            .OrderBy(kv => GetPropertyOrder(httpContext, kv.Key))
            .ToDictionary(kv => kv.Key, kv => kv.Value);

        problemDetails.Errors.Clear();
        foreach (var error in orderedErrors)
        {
            problemDetails.Errors.Add(error.Key, error.Value);
        }

        return problemDetails;
    }

    private int GetPropertyOrder(HttpContext httpContext, string propertyName)
    {
        if (httpContext?.RequestServices.GetService<IModelMetadataProvider>() is not IModelMetadataProvider metadataProvider)
            return int.MaxValue;

        // 替换为你的实体类类型(包含CompanyName等属性的类)
        var modelType = typeof(YourBusinessModel); 
        var metadata = metadataProvider.GetMetadataForProperty(modelType, propertyName);
        return metadata.Order;
    }

    private void ApplyProblemDetailsDefaults(HttpContext httpContext, ProblemDetails problemDetails, int statusCode)
    {
        problemDetails.Status ??= statusCode;

        if (_options.ClientErrorMapping.TryGetValue(statusCode, out var clientErrorData))
        {
            problemDetails.Title ??= clientErrorData.Title;
            problemDetails.Type ??= clientErrorData.Link;
        }

        var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
        if (traceId != null)
        {
            problemDetails.Extensions["traceId"] = traceId;
        }
    }
}

注意:将YourBusinessModel替换为你实际使用的实体类。若涉及嵌套模型,需调整GetPropertyOrder方法中的模型类型获取逻辑。

4. 注册自定义问题细节工厂

在Program.cs中替换默认工厂:

builder.Services.AddSingleton<ProblemDetailsFactory, OrderedProblemDetailsFactory>();

完成配置后,API返回的验证错误会严格按照类中属性的定义顺序排列,先返回CompanyName的错误,再依次是CompanyCode、Address等。

内容的提问来源于stack exchange,提问作者Mh Dip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 12:01:52