Reputation: 2834
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
Upvotes: 17
Views: 11394
Reputation: 1770
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;
}
Upvotes: 19
Reputation: 201
1- on constructor put this code
this.ClassLevelCascadeMode = CascadeMode.Stop;
means next rulefor not implemented only if first rulefor is correct.
2- rulefor PincodeIsUnique not implemented only if first condition is correc bec(.Cascade(CascadeMode.Stop)) and in first rulefor conditions are implemented as one by one.
RuleFor(c => c.pincode)
.Cascade(CascadeMode.Stop)
.NotEmpty().WithMessage("Pincode is required.")
.NotNull()
.Length(3, 6).WithMessage("Pincode between 3 and 6 characters.")
.Must(BeAValidPincode).WithMessage("Please specify a valid pincode.");
RuleFor(c => c)
.MustAsync(PincodeIsUnique)
.WithMessage("A picode with the same number already exists.");
Upvotes: 1
Reputation: 724
CascadeMode = CascadeMode.Stop //This is obselete now
Use this :
RuleLevelCascadeMode = CascadeMode.Stop;
Upvotes: 0
Reputation: 2834
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.
Upvotes: 14
Reputation: 4125
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..
Upvotes: 2