-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
FluentValidation version
11.9.0
ASP.NET version
No response
Summary
I think there is a problem with validators not running the rules they should when running on a collection with RuleForEach and using SetValidator on those items when the child validator has ClassLevelCascadeModel = CascadeMode.Stop.
It seems like if a rule is stopped for an item earlier in the collection, it is never run for any items later on even if it should.
Steps to Reproduce
In this example, the validator validates that both the First and Second properties are true. It has a cascade mode to stop, so if First is not valid (false), it won't check Second. The IsValid method will also log to the console that it's been called.
using FluentValidation;
new MyParentTestModelValidator().ValidateAndThrow(new MyParentTestModel(new MyTestModel[]
{
new(true, true), // Should be fine
new(true, false), // Should say that Models[1].Second has an error
new(false, false), // Should say that Models[2].First has an error, should not run 'Second' validation.
new(true, false), // Should say that Models[3].Second has an error
new(false, false), // Should say that Models[4].First has an error, should not run 'Second' validation
new(true, true) // Should be fine
}));
internal record MyTestModel(bool First, bool Second);
internal record MyParentTestModel(IEnumerable<MyTestModel> Models);
internal class MyTestModelValidator : AbstractValidator<MyTestModel>
{
public MyTestModelValidator()
{
ClassLevelCascadeMode = CascadeMode.Stop;
RuleFor(x => x.First).Must(IsValid);
RuleFor(x => x.Second).Must(IsValid);
}
private static bool IsValid(MyTestModel _, bool isValid, ValidationContext<MyTestModel> context)
{
Console.WriteLine($"Running validation logic for '{context.PropertyPath}'");
return isValid;
}
}
internal class MyParentTestModelValidator : AbstractValidator<MyParentTestModel>
{
public MyParentTestModelValidator()
{
RuleForEach(x => x.Models).SetValidator(new MyTestModelValidator());
}
}The console output of this is
Running validation logic for 'Models[0].First'
Running validation logic for 'Models[0].Second'
Running validation logic for 'Models[1].First'
Running validation logic for 'Models[1].Second'
Running validation logic for 'Models[2].First'
Running validation logic for 'Models[3].First'
Running validation logic for 'Models[4].First'
Running validation logic for 'Models[5].First'
Unhandled exception. FluentValidation.ValidationException: Validation failed:
-- Models[1].Second: The specified condition was not met for 'Second'. Severity: Error
-- Models[2].First: The specified condition was not met for 'First'. Severity: Error
-- Models[4].First: The specified condition was not met for 'First'. Severity: Error
As we can see, once the rule for First has failed once (in this case at item [2]), the system will not again run the validation rule for Second.
I would expect in this example, that it should run validation logic on Models[3].Second (since Models[3].First is true), and write a Models[3].Second error to the exception. I would also expect it to see in the console that it ran on Models[5].Second (again, because Models[5].First is true) but that is not the case.
I have also tried constructing a new validator for each child item (RuleForEach(x => x.Models).SetValidator(_ => new MyTestModelValidator())), but that does not seem to change the behaviour.