Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally validate collection

public class ProspectValidator : AbstractValidator<Prospect>
{
    public ProspectValidator()
    {   
        RuleFor(p => p.CompetitorProducts)
            .NotNull()
            .When(p => !p.ExistingCustomer);

        RuleFor(p => p.CompetitorProducts.Count)
            .GreaterThan(0)
            .When(p => p.CompetitorProducts != null && !p.ExistingCustomer);
    }
}

This validator checks that if ExistingCustomer is false then CompetitorProducts is not null and has at least one element.

It works but is it possible to write this as one rule?

like image 238
Ian Warburton Avatar asked Mar 22 '23 03:03

Ian Warburton


1 Answers

You have two options for doing this in a single command, but they are both a bit tricky as you need to validate an inner property while validating that the encompassing class is not null. They both revolve around the Cascade property (see "Setting the Cascade Mode") to stop the validation on the first error.

First, you can use Must to do the validation. You will need to specify a WithMessage as I've done to avoid getting a generic "The specified condition was not met for 'Competitor Products'." error. You may also want to override the WithErrorCode as it defaults to Predicate. Note that this will only show on the second validation error; the first error will still correctly return the message that the property must not be null.

RuleFor(p => p.CompetitorProducts)
  .Cascade(CascadeMode.StopOnFirstFailure)
  .NotNull()
  .Must(p => p.Count > 0)
  .WithMessage("{PropertyName} must be greater than '0'")
  .When(p => !p.ExistingCustomer);

Second, you can provide a validator for the CompetitorProducts class as a whole. This will allow you to have FluentValidation manage the error message and code. This will work well if you have other validations that must occur on the class, but might be overkill if you just need to validate the single property.

  public class ProspectValidator: AbstractValidator<Prospect>
    {
        public CurrentUserValidator()
        {
            RuleFor(p => p.CompetitorProducts)
              .Cascade(CascadeMode.StopOnFirstFailure)
              .NotNull()
              .SetValidator(new CompetitorProductsValidator())
              .When(p => !p.ExistingCustomer);
        }
    }

    public class CompetitorProductsValidator : AbstractValidator<Prospect.CompetitorProducts>
    {
        public CompetitorProductsValidator()
        {
            RuleFor(p => p.Count)
              .GreaterThan(0);
        }
    }
like image 148
bpruitt-goddard Avatar answered Apr 06 '23 22:04

bpruitt-goddard