I have a model:
public class DTO
{
public int[] StatementItems { get; set; }
}
Which I want to validate that:
StatementItems is not nullStatementItems is not empty StatementItems does not contain any duplicate IDsThe validation rule chain I created is:
RuleFor(x => x.StatementItems).NotNull().NotEmpty().Must(x => x.Distinct().Count() == x.Count());
And I have a test as:
_validator.ShouldHaveValidationErrorFor(x => x.StatementItems, null as int[]);
When I run the test passing in a null value, I would expect it to fail on the first rule of the chain (NotNull()) and stop there. However, it complains that the lamda value used in the Must() is null.
Am I wrong in thinking that the Must() shouldn't be run if the NotNull() fails? If so, how should this rule be written?
Thanks
Check out FluentValidation's cascade mode. You can make it short-circuit on the first failure like this:
this.RuleFor(x => x.StatementItems)
.Cascade(CascadeMode.Stop)
.NotNull()
.NotEmpty()
.Must(x => x.Distinct().Count() == x.Count());
Also, you can configure this in your AbstractValidator subclass's constructor. Then you won't need to put it on every rule.
public MyInputValidator()
{
this.CascadeMode = CascadeMode.Stop;
}
Although @NPras's answer did supply my with a solution, I didn't like the fact that I'm duplicating the NotNull rule. After a bit more research on FluentValidation I have implemented it using DependentRules:
RuleFor(x => x.StatementItems).NotNull().NotEmpty()
.DependentRules(d =>
d.RuleFor(x => x.StatementItems).Must(x => x.Distinct().Count() == x.Count())
);
So now the Must condition is only fired when the previous two rules are valid.
I don't see in the FluentValidation documentation that it actually guarantees short-circuiting.
If you look in its source:
public virtual ValidationResult Validate(ValidationContext<T> context)
{
...
var failures = nestedValidators.SelectMany(x => x.Validate(context));
return new ValidationResult(failures);
}
It will run through *all* the validators (with the SelectMany()) and returns a list of errors.
Your only option seems to be to force a check on your Must rule.
.Must(x => x!= null && x.Distinct().Count() == x.Count())
//or, fluently:
.Must(x => x.Distinct().Count() == x.Count()).When(x => x! = null)
EDIT:
I was going to suggest that since Validate() is virtual, you could just override it in your validator to make it short-circuit. But then I realised that the nestedValidators list is private. So yeah, no..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With