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