35

i'm defining a validation for my Request objects. I would like the validator to stop on the very first failure, not only the one on the same chain. In the example below, if my TechnicalHeader object is null, i get a NullReference exception when the validation reaches the rule for TechnicalHeader.MCUserid.

In poor words, i would like to do a conditional validation over the last three rules in the code below, according to the result of the first rule

using System;
using ServiceStack.FluentValidation;
using MyProj.Services.Models;

namespace MyProj.Services.BaseService.Validators
{
    public class BaseValidator<T> : AbstractValidator<T>
        where T : RequestBase
    {
        public BaseValidator()
        {
            RuleSet(ServiceStack.ApplyTo.Put | ServiceStack.ApplyTo.Post, 
                () =>
                {
                    this.CascadeMode = CascadeMode.StopOnFirstFailure;
                    RuleFor(x => x.TechnicalHeader).Cascade(CascadeMode.StopOnFirstFailure).NotNull().WithMessage("Header cannot be null");
                    RuleFor(x => x.TechnicalHeader).NotEmpty().WithMessage("Header cannot be null");
                    RuleFor(x => x.TechnicalHeader.Userid).NotEmpty().WithMessage("Userid cannot be null or an empty string");
                    RuleFor(x => x.TechnicalHeader.CabCode).GreaterThan(0).WithMessage("CabCode cannot be or less than 0");
                    RuleFor(x => x.TechnicalHeader.Ndg).NotEmpty().WithMessage("Ndg cannot be null or an empty string");
                }
            );
        }
    }
}
pizzaboy
  • 995
  • 1
  • 9
  • 20

3 Answers3

51

Just check for null before running the rules that depend on them, using a When condition.

this.CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.TechnicalHeader).NotNull().WithMessage("Header cannot be null");

// Ensure TechnicalHeader is provided
When(x => x.TechnicalHeader != null, () => {
    RuleFor(x => x.TechnicalHeader.Userid).NotEmpty().WithMessage("Userid cannot be null or an empty string");
    RuleFor(x => x.TechnicalHeader.CabCode).GreaterThan(0).WithMessage("CabCode cannot be or less than 0");
    RuleFor(x => x.TechnicalHeader.Ndg).NotEmpty().WithMessage("Ndg cannot be null or an empty string");
});
Scott
  • 21,211
  • 8
  • 65
  • 72
  • Thanks, i supposed it, but i thought there was a way to tell the validator to stop.BTW, do you know something about http://www.nudoq.org/#!/Packages/FluentValidation-Signed/FluentValidation/DefaultValidatorOptions/M/OnAnyFailure%28T,TProperty%29 ? What's the "OnAnyFailure" used for? – pizzaboy Feb 07 '14 at 07:05
  • 5
    @Pizzaboy `CascadeMode.StopOnFirstFailure` can only apply to the **current rule chain**, not subsequent rules. When you change the subject of the rule i.e. `RuleFor(x = x.TechnicalHeader)` to `RuleFor(x = x.TechnicalHeader.UserId)` you have to start a new chain. FluentValidation isn't a substitute for checking for null, using the `When` clause covers this check. `OnAnyFailure` isn't used in ServiceStack's FluentValidation which is a subset of the FluentValidation package you linked too. – Scott Feb 07 '14 at 09:28
16

You can use DependentRules.

RuleFor(object => object.String)
    .NotNull()
    .DependentRules(() =>
    {
        RuleFor(object => object.String)
            .NotEmpty()
            .Matches("^[A-Z]{3}$");
    });

Then you dont duplicate validation code.

You can even add an extension method for no duplicating the RuleFor.

    public static IRuleBuilderOptions<T, TProperty> DependentRules<T, TProperty>(
        this IRuleBuilderOptions<T, TProperty> currentRule, 
        Action<IRuleBuilderOptions<T, TProperty>> action)
    {
        return currentRule.DependentRules(() => action(currentRule));
    }

So the definitve code:

RuleFor(object => object.String)
    .NotNull()
    .DependentRules(currentRule =>
    {
        currentRule
            .NotEmpty()
            .Matches("^[A-Z]{3}$");
    });
  • how can I use this if in my new validator I include other validator and one more extra rule? Include(new DtoValidator()); RuleFor(x => x).MustAsync ...... – Hanieh Variani Jan 18 '23 at 08:47
7

Just add SetValidator before running the rules that depend on them, using a Custom condition:

//if InpatOrderImportValidatorBllNode fail ,the custom method cannot be executed
RuleFor(x => x).Cascade(CascadeMode.StopOnFirstFailure)
               .SetValidator(new InpatOrderImportValidatorBllNode())
               .Custom((input, context) => {          
                  context.AddFailure(new ValidationFailure("DrugTypeName", "fail"));
               });
barbsan
  • 3,418
  • 11
  • 21
  • 28
Francis Shaw
  • 101
  • 1
  • 3