ModelState.IsValid 总是 true 是因为验证未触发,仅在框架模型绑定时自动执行;手动 new 对象、漏设 validateAllProperties:true 或属性无 getter 均导致验证失效。

ModelState 验证不是“自动生效”的魔法,它只在 ASP.NET Core / MVC 的模型绑定流程中被触发;手动 new 对象、绕过绑定、漏掉 validateAllProperties: true,都会导致验证静默失效。
ModelState.IsValid 为什么总是 true?
这不是 ModelState 有问题,而是验证根本没跑起来。框架只在以下场景自动填充 ModelState:
- 控制器 Action 参数是强类型(如
User),且带[FromBody]、[FromForm]、[FromQuery]或路由绑定(如[Route("api/users/{id}")]) - 参数由框架反序列化并完成属性赋值,而非你在 Action 内手动
new User()后逐个设值 - 模型属性有 public getter —— 没 getter 的属性会被验证器跳过
❌ 错误写法:public IActionResult Create() { var u = new User(); u.Name = Request.Form["name"]; } → ModelState.IsValid 永远为 true,所有 DataAnnotations 特性形同虚设。
[Required] 对 int 和 DateTime 为啥不报错?
因为 [Required] 判定逻辑是 “是否为 null”,而 int 默认值是 0,DateTime 默认是 DateTime.MinValue,它们永远不为 null。
- 想校验数值字段“非零”或“真实传入”,改用
[Range(1, int.MaxValue)]或声明为可空类型:public int? Age { get; set; }+[Required] - 前端传
{"age": null},后端仍得int age = 0—— 这是 JSON 反序列化默认行为,不是验证失败 -
[StringLength]和[Range]类型严格:前者只用于string,后者只用于数值类型;混用会编译报错或静默跳过
非 Controller 层怎么手动验证?
Service、Blazor 组件、控制台工具类里没有 ModelState,必须显式调用 Validator.TryValidateObject:
var context = new ValidationContext(dto);
var results = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(
dto,
context,
results,
validateAllProperties: true); // ⚠️ 必须为 true,否则 null/empty 字符串的 [Required] 不触发
注意:
- 结果中的
MemberNames是属性名(如"Email"),不是显示名;若需中文提示,应配合资源文件或自定义DisplayName特性 -
ErrorMessage若含占位符(如"{0} 不能为空"),必须确保特性构造时传入DisplayName,否则{0}不展开 - 此方式不支持异步、无法注入服务 —— 查库类业务规则(如“邮箱是否已注册”)应交由
IValidatableObject或 FluentValidation 处理
跨字段验证该用 IValidatableObject 还是自定义 Attribute?
优先用 IValidatableObject,尤其当逻辑涉及多个属性或需访问外部服务时:
-
IValidatableObject.Validate在所有字段级验证(DataAnnotations)通过后才执行,天然适合“结束时间晚于开始时间”这类判断 - 可通过
validationContext.GetService<IUserService>()获取 DI 服务,做查库、发短信等操作 - 返回
new ValidationResult("错误信息", new[] { nameof(EndDate) }),能让前端精准定位到字段 - 自定义
ValidationAttribute更适合单字段语义规则(如“密码强度”),但无法直接使用 DI,强行注入会破坏测试性和生命周期管理
最易被忽略的一点:字段级验证和业务级验证是两层事。[Required] 拦不住“已注册的手机号”,IValidatableObject 也救不了“空字符串提交”——必须分层设计,不能指望一个机制包打天下。


















