.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
相关产品推荐
相关产品推荐

