FluentValidation链式自定义验证规则问题:SmartEnum验证异常
这个问题我之前也碰到过,核心原因是**FluentValidation默认的规则级联行为(CascadeMode)**在搞鬼!
默认情况下,FluentValidation的CascadeMode是Continue——意思是不管前面的验证规则有没有失败,后面的规则都会照常执行。哪怕你用链式调用把NotEmpty和Must连在一起,它也不会自动在NotEmpty失败时“短路”停止后续验证,所以当Feeling为null时,两条规则都会触发错误提示,就出现了你看到的情况。
你说拆分成两个独立规则时“正常”,大概率是你在拆分后的Must规则里隐含加了null判断(比如用When限定只有非null时才验证),不然默认情况下拆分的规则其实也会全部执行,返回两条错误。
接下来给你两种解决思路:
方案1:显式设置CascadeMode为StopOnFirstFailure
你可以在当前的RuleFor链上设置级联模式为StopOnFirstFailure,这样一旦前面的规则验证失败,后面的规则就会直接跳过,不会再执行:
RuleFor(x => x.Feeling) .Cascade(CascadeMode.StopOnFirstFailure) // 关键:设置级联模式 .NotEmpty().WithMessage("Feeling不能为空") .Must(f => f.Value == 1).WithMessage("Feeling必须为指定值");
如果是你的扩展方法ValidateFeeling,可以把这个配置加到扩展方法里,这样所有调用这个扩展的地方都会自动生效:
public static IRuleBuilderOptions<T, FeelingSystemType> ValidateFeeling<T>(this IRuleBuilder<T, FeelingSystemType> ruleBuilder) { return ruleBuilder .Cascade(CascadeMode.StopOnFirstFailure) .NotEmpty().WithMessage("Feeling不能为空") .Must(f => f.Value == 1).WithMessage("Feeling必须为指定值"); }
方案2:给Must规则添加When条件
另一种方式是给Must规则加上执行条件,只有当Feeling不为null时才执行后续验证:
RuleFor(x => x.Feeling) .NotEmpty().WithMessage("Feeling不能为空") .Must(f => f.Value == 1).WithMessage("Feeling必须为指定值") .When(f => f != null); // 限定仅当非null时执行Must验证
这里提一句,对于Ardalis.SmartEnum这种引用类型,用NotNull()代替NotEmpty()会更准确(因为NotEmpty()主要针对字符串、集合这类有“空”概念的类型,引用类型的非空检查用NotNull()语义更清晰):
RuleFor(x => x.Feeling) .NotNull().WithMessage("Feeling不能为空") .Must(f => f.Value == 1).WithMessage("Feeling必须为指定值") .When(f => f != null);
补充说明
FluentValidation的链式调用本身是支持的,但它的默认行为是执行所有规则,这是为了能一次性返回所有验证错误。如果你需要短路,就必须显式设置CascadeMode或者添加条件判断,这也是框架设计的一个权衡点。
内容的提问来源于stack exchange,提问作者JGillardCCG

