Reputation: 940
I have this validator:
public class InputValidator : AbstractValidator<InputData>
{
public InputValidator()
{
RuleFor(inputData => inputData.Ucl).GreaterThan(0).....;
RuleForEach(inputData => inputData.Loads).ChildRules(inputData => {
inputData.RuleFor(load => load.Position).GreaterThan(0).....);
});
... etc
However: Position (in each load) must also be less than Ucl (in InputData). How can I make a rule for such a relation (parent parameter vs. child parameter)?
Upvotes: 5
Views: 8165
Reputation: 3598
By using Root Context Data, it's also possible to set any arbitrary object on the parent context during pre-validation. You could then access this object through the context parameter during evaluation of a custom rule.
A simple example would look something like:
public class InputValidator : AbstractValidator<InputData>
{
protected override bool PreValidate(ValidationContext<InputData> context, ValidationResult result)
{
//RootContextData is a Dictionary<string, object> which can have any arbitrary objects added in during prevalidation
context.RootContextData["InputDataBeingValidated"] = context.InstanceToValidate;
return base.PreValidate(context, result);
}
public InputValidator()
{
RuleFor(inputData => inputData.Ucl).GreaterThan(0); //....
RuleForEach(inputData => inputData.Loads).ChildRules(inputData => {
inputData.RuleFor(load => load.Position).GreaterThan(0)
.Custom((pos, ctx) =>
{
var currentInstance = ctx.ParentContext.RootContextData["InputDataBeingValidated"];
if(pos >= currentInstance.Ucl)
{
ctx.AddFailure("Build your error message here");
}
}
);
);
Upvotes: 3
Reputation: 3213
I don't think there is a nice way to do it inline. Child rules doesn't allow you to pass in the parent object. Must
and I think Custom
(via context.ParentContext.InstanceToValidate
maybe) would allow you to add rules(s) involving both the parent and child but the rule(s) would be against the collection rather than each item. The better way, and how I'd normally do it would be to create a child validator for your Load
entity:
public class LoadValidator : AbstractValidator<Load>
{
public LoadValidator(InputData inputData)
{
RuleFor(load => load.Position)
.GreaterThan(0).WithMessage("Position must be greater than 0")
.LessThan(inputData.Ucl).WithMessage("Position must be less than Ucl");
}
}
This becomes reusable and a lot easier to test. Then use SetValidator
to use it.
public class InputDataValidator : AbstractValidator<InputData>
{
public InputDataValidator()
{
RuleFor(inputData => inputData.Ucl)
.GreaterThan(0).WithMessage("Ucl must be greater than 0");
RuleForEach(inputData => inputData.Loads)
.SetValidator(inputData => new LoadValidator(inputData));
}
}
Reusable property validators could be another way of doing it, but for me it'd have to be a reasonably high level/generic case to bother with implementing one.
Upvotes: 9