ASP.NET Core 8 Web API数据验证为何忽略Data Annotations自定义错误信息?
解决ApiController中DataAnnotations自定义错误信息不生效的问题
你遇到的核心问题是C# required关键字与DataAnnotations验证的执行时机冲突:
required是JSON反序列化阶段的检查,由System.Text.Json在创建对象时触发,一旦发现缺少标记为required的属性,直接抛出系统默认错误。[Required]是Model验证阶段的检查,只有当对象成功完成反序列化后,ASP.NET Core才会执行DataAnnotations验证逻辑。
当你发送空对象{}时,反序列化器先报错,根本没走到Model验证环节,自然不会返回你配置的自定义错误信息。
下面提供三种可行的解决办法:
方案1:移除required关键字,仅保留[Required]特性
修改UpdateAboutPayload,去掉属性的required修饰符,允许属性为null,让反序列化能成功创建对象,之后Model验证会触发[Required]的自定义错误:
using System.ComponentModel.DataAnnotations; namespace ClassLibrary.Models.API.Requests { public class UpdateAboutPayload { [Required(ErrorMessage = "About is required")] [StringLength(500, ErrorMessage = "About must be between 0 and 500 characters long")] public string? About { get; set; } } }
修改后发送{},会返回你配置的自定义错误:
{ "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "About": [ "About is required" ] }, "traceId": "..." }
方案2:配置System.Text.Json禁用required属性的强制检查(.NET 7+)
如果你想保留required关键字,可以在Program.cs中配置Json选项,让反序列化器不强制要求required属性,从而让Model验证接管检查逻辑:
builder.Services.AddControllers() .AddJsonOptions(options => { // 核心配置:禁用required属性的反序列化强制检查 options.JsonSerializerOptions.RequiredPropertySelector = static (property) => false; // 可选:保持属性名大小写不敏感 options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; });
方案3:自定义ProblemDetailsFactory替换错误信息
如果必须保留required关键字的反序列化检查,同时想自定义错误信息,可以实现自定义的ProblemDetailsFactory,替换默认的错误内容:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Diagnostics; public class CustomProblemDetailsFactory : ProblemDetailsFactory { private readonly ApiBehaviorOptions _options; public CustomProblemDetailsFactory(IOptions<ApiBehaviorOptions> options) { _options = options.Value ?? throw new ArgumentNullException(nameof(options)); } public override ProblemDetails CreateProblemDetails( HttpContext httpContext, int? statusCode = null, string? title = null, string? type = null, string? detail = null, string? instance = null) { statusCode ??= 500; var problemDetails = new ProblemDetails { Status = statusCode, Title = title, Type = type, Detail = detail, Instance = instance, }; ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value); 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)); } statusCode ??= 400; var problemDetails = new ValidationProblemDetails(modelStateDictionary) { Status = statusCode, Type = type, Detail = detail, Instance = instance, }; if (title != null) { problemDetails.Title = title; } ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value); // 替换反序列化阶段的默认错误信息 if (problemDetails.Errors.TryGetValue("$", out var errors)) { problemDetails.Errors.Remove("$"); problemDetails.Errors.Add("About", new[] { "About is required" }); } // 移除多余的payload错误提示 problemDetails.Errors.Remove("payload"); return problemDetails; } 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; } } }
然后在Program.cs中注册这个自定义工厂:
builder.Services.AddSingleton<ProblemDetailsFactory, CustomProblemDetailsFactory>();
内容的提问来源于stack exchange,提问作者iKingNinja
相关产品推荐
相关产品推荐

