asp.net core模型验证仅在框架自动绑定时触发,手动构造对象不生效;int/datetime因有默认值不满足required判定,需改用可空类型或range;手动验证须设validateallproperties=true,属性需public get,json字段名不匹配需jsonproperty映射;业务规则应放服务层或fluentvalidation。

[Required] 不报错?验证根本没跑起来
加了 [Required] 却没触发错误,不是注解失效,而是验证流程压根没启动。ASP.NET Core 的验证只在模型由框架自动绑定时才激活——比如用 [FromBody]、[FromForm] 或路由参数接收对象。手动 new User() 再逐个赋值,Validator.TryValidateObject 完全不会被调用,ModelState.IsValid 永远为 true。
- ✅ 正确:控制器方法签名写成
public IActionResult Create([FromBody] User user),框架自动反序列化 + 验证 + 填充ModelState - ❌ 错误:写成
public IActionResult Create(),再用Request.Form["name"]手动构造对象 - ⚠️ 注意:若继承
ControllerBase(非Controller),默认不启用视图相关功能,但模型验证仍生效——前提是用了正确的绑定属性
int 和 DateTime 总是“通过”?因为它们默认就有值
[Required] 只检查“是否为 null”或“是否为空字符串”,而 int 默认是 0,DateTime 默认是 DateTime.MinValue,永远不满足“空”的判定条件。这不是 bug,是类型语义决定的。
- 想让数值字段必填 → 改成可空类型:
public int? Age { get; set; },再加[Required] - 想限制日期不能是默认值 → 用
[Range(typeof(DateTime), "1900-01-01", "2100-12-31")],或手动检查user.BirthDate != DateTime.MinValue - 前端传
{"age": null},后端仍是int age = 0—— 这是 JSON 反序列化行为,和验证无关;要避免,就得用int?
手动验证时漏掉 validateAllProperties = true
在控制台、类库或自定义验证场景中调用 Validator.TryValidateObject,第四个参数 validateAllProperties 必须设为 true,否则验证器只检查“当前有值”的属性,跳过 null 或空字符串字段,导致 [Required] 形同虚设。
- 正确调用:
Validator.TryValidateObject(model, new ValidationContext(model), results, true) - 属性必须有
public get,否则ValidationContext读不到值,直接跳过该属性 - JSON 字段名与 C# 属性名不一致(如前端传
user_name,后端是UserName)→ 验证器找不到对应属性,等价于没加注解;解决方式:加[JsonProperty("user_name")]或全局配置SnakeCaseNamingStrategy
业务规则别塞进 DataAnnotations
[Required]、[StringLength] 这些只管结构合法性:字段有没有、长度对不对、格式符不符合正则。查数据库唯一性、判断权限、校验状态流转(比如“结束时间必须晚于开始时间”),都得放在服务层处理。
- 邮箱是否已注册?→ 在 service 中查库,失败时调
ModelState.AddModelError("Email", "该邮箱已被使用") - 时间范围逻辑?→ 不要用一堆
[Range]拼凑,写个自定义ValidationAttribute,重写IsValid,从validationContext.ObjectInstance取出整个模型做判断 - FluentValidation 更适合这类逻辑:可注入服务、支持异步、不污染实体、单元测试友好;DataAnnotations 留给简单标记即可
最容易被忽略的是:验证特性本身不执行任何 IO 或业务判断,它只是声明式契约。真正跑逻辑的地方,永远在你手动写的 service 方法里,或者 FluentValidation 的 RuleFor 回调中。










