-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
FluentValidation version
11.5.1
ASP.NET version
No response
Summary
We've been working on migrating a project that was previously using FluentValidation 10.3.0 to the newest version. In this project, we have a lot of rulesets that run rules on deeply-nested complex objects. After migrating to 11.5.1, some of our validation has stopped working. We've narrowed it down to an issue with nested complex objects in a nested RuleForEach and ChildRules chain.
Looking at the changelogs, we haven't been able to find any mention of whether this is intentional or not, and most web searches lead to issues with SetValidator(), which isn't something we're using in our project. Porting the current rules over to individual validators would be very time consuming, so we'd like to avoid it for now, if possible.
Our problem is as follows:
Given classes with lists of complex objects that have their own lists of complex objects (and so on), nested usage of RuleForEach and ChildRules breaks when inside a RuleSet, but works otherwise.
Additionally, adding the "default" ruleset rule to the validator seems to run these child rules, even if they are only declared in a ruleset. Note that this doesn't fix our issue, because it would run all rules that are in the "default" ruleset as well, which is something we don't want.
I expect that this behavior should either work in rulesets, or not work outside of them. It shouldn't work in one case and not in the other.
I am unsure if it supposed to work in the first place, and that we've been relying on a bug to get our current validation working.
Steps to Reproduce
Code sample
Below is a trivial code example that showcases the problem:
public static class Program
{
public class Foo
{
public List<string> Names { get; set; } = new();
}
public class Bar
{
public List<Foo> Foos { get; set; } = new();
}
public class Baz
{
public List<Bar> Bars { get; set; } = new();
}
public class WorkingValidator : AbstractValidator<Baz>
{
public WorkingValidator()
{
RuleForEach(baz => baz.Bars)
.ChildRules(barRule => barRule.RuleForEach(bar => bar.Foos)
.ChildRules(fooRule => fooRule.RuleForEach(foo => foo.Names)
.ChildRules(name => name.RuleFor(n => n)
.NotEmpty()
.WithMessage("Name is required"))));
}
}
public class BrokenValidator : AbstractValidator<Baz>
{
public const string RuleSetName = "Broken-Ruleset-Name";
public BrokenValidator()
{
RuleSet(RuleSetName, () =>
{
RuleForEach(baz => baz.Bars)
.ChildRules(barRule => barRule.RuleForEach(bar => bar.Foos)
.ChildRules(fooRule => fooRule.RuleForEach(foo => foo.Names)
.ChildRules(name => name.RuleFor(n => n)
.NotEmpty()
.WithMessage("Name is required"))));
});
}
}
public static void Main()
{
var foos = new List<Foo>()
{
new() { Names = { "Bob" }},
new() { Names = { string.Empty }},
};
var bars = new List<Bar>()
{
new(),
new() { Foos = foos }
};
var baz = new Baz()
{
Bars = bars
};
var workingValidator = new WorkingValidator();
var workingResult = workingValidator.Validate(baz);
if (workingResult.IsValid)
{
Console.WriteLine("WorkingValidator - Valid");
}
else
{
Console.WriteLine("WorkingValidator - Invalid");
foreach (var error in workingResult.Errors)
{
Console.WriteLine(error.ErrorMessage);
}
}
var brokenValidator = new BrokenValidator();
var brokenResult = brokenValidator.Validate(baz, options => options.IncludeRuleSets(BrokenValidator.RuleSetName));
if (brokenResult.IsValid)
{
Console.WriteLine("BrokenValidator - Valid");
}
else
{
Console.WriteLine("BrokenValidator - Invalid");
foreach (var error in brokenResult.Errors)
{
Console.WriteLine(error.ErrorMessage);
}
}
}
}Expected output (FluentValidation 11.5.1)
I would expect the following output:
WorkingValidator - Invalid
Name is required
BrokenValidator - Invalid
Name is required
Or, in the events that this behavior isn't supported:
WorkingValidator - Valid
BrokenValidator - Valid
Actual output (FluentValidation 11.5.1)
WorkingValidator - Invalid
Name is required
BrokenValidator - Valid
Original output (FluentValidation 10.3.0)
WorkingValidator - Invalid
Name is required
BrokenValidator - Invalid
Name is required