[required]未报错是因为验证未触发或类型默认值非null:int/datetime默认为0/minvalue,需改用可空类型或range验证;验证仅在[frombody]等框架绑定时生效,手动赋值或无绑定特性将跳过验证。

加了 [Required] 却没报错?不是注解失效,是验证根本没跑起来——90% 的问题出在绑定方式或类型默认值上。
为什么 [Required] 对 int 和 DateTime 总是通过
因为 [Required] 只检测 null 或空字符串,而 int 默认是 0,DateTime 默认是 DateTime.MinValue,它们永远“有值”。
- 想让数值字段真正必填 → 改成可空类型:
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 反序列化行为,不是验证问题
[FromBody] 是触发验证的硬性前提
验证管道只在框架接管反序列化时启动。手动 new 对象再赋值,[Required] 形同虚设。
- ✅ 正确写法:
public IActionResult Create([FromBody] User user)—— 框架自动调用Validator.TryValidateObject并填充ModelState - ❌ 错误写法:
var user = new User(); user.Name = Request.Form["name"];—— 验证器完全不介入 - 控制器若继承
ControllerBase,必须确保参数带[FromBody]或[FromForm],否则跳过验证管道
手动验证时漏掉 validateAllProperties: true
在 Service、DTO 工具类等非 Controller 场景下,必须显式调用 Validator.TryValidateObject,且第四个参数必须为 true。
- 不传或传
false→ 只校验“有值”的属性,[Required]对空字符串或null字段直接跳过 - 属性没有
public get→ValidationContext读不到值,该字段被忽略(private set没问题) - JSON 字段名和 C# 属性名不匹配(如前端传
user_name,后端是UserName)→ 验证器找不到对应属性,相当于没加注解;可用[JsonProperty("user_name")]或全局配置SnakeCaseNamingStrategy
业务规则别塞进 DataAnnotations
[Required]、[StringLength] 只管结构合法性,查库唯一性、状态流转、跨字段逻辑(如“结束时间晚于开始时间”)都得在服务层做。
- 邮箱是否已注册?不能靠注解 → 在 service 里查库,失败时调
ModelState.AddModelError("Email", "该邮箱已被使用") - 结束时间必须晚于开始时间?应封装为自定义特性,或用
FluentValidation的RuleFor(x => x.EndTime).GreaterThan(x => x.StartTime) - 所有数据库交互、HTTP 调用、权限判断,都不适合放在
ValidationAttribute.IsValid()里 —— 它不该有 IO,也不支持异步
最易被忽略的是:验证特性本身不执行任何逻辑,它只是元数据;真正干活的是绑定流程或你手写的 TryValidateObject 调用。一旦绑定没走通,或类型默认值挡在前面,就只剩静默通过。











