Erik
Erik

Reputation: 940

FluentValidation: Using a parent property value in a child collection rule

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

Answers (2)

Tom Warner
Tom Warner

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

rgvlee
rgvlee

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

Related Questions