JGillardCCG
JGillardCCG

Reputation: 23

FluentValidation Custom Validation Rule chain issue

I am trying to use a custom validator to validate a smart enum (Ardalis.SmartEnum)

Here is the class for the enum:

public abstract class FeelingSystemType : SmartEnum<FeelingSystemType>
{
    public static FeelingSystemType Positive = new PositiveType();
    public static FeelingSystemType Negative = new NegativeType();

    private FeelingSystemType(string name, int value) : base(name, value) { }

    private class PositiveType : FeelingSystemType
    {
        internal PositiveType() : base(nameof(Positive), 1) { }
    }

    private class NegativeType : FeelingSystemType
    {
        internal NegativeType() : base(nameof(Negative), 2) { }
    }
}

This is the command :

public class Command : IRequest<CommandResponsem>
{
    public Command() { }

    [JsonConverter(typeof(SmartEnumNameConverter<FeelingSystemType, int>))]
    public FeelingSystemType Feeling { get; set; }
}

This is the command validator:

public class CommandValidator : AbstractValidator<Command>
{
    public CommandValidator()
    {
        RuleFor(r => r.Feeling).ValidateFeeling();
    }
}

And this is the custom validation rule:

public static IRuleBuilder<T, FeelingSystemType> ValidateFeeling<T>(this IRuleBuilder<T, FeelingSystemType> rule)
{
    return rule
        .NotEmpty()
            .WithMessage("A Feeling must be entered")
        .Must(feeling => typeof(FeelingSystemType).IsAssignableFrom(feeling?.GetType()))
            .WithMessage("Please enter a valid Feeling");
}

When I'm sending the request the validator seems to be ignoring that NotEmpty preceeds the Must part of the rule and still continues to validate the Must even when Feeling is null, but is returning both messages. I've tried doing the validation within the command validator and I get the same result unless I do the NotEmpty and Must as two separate rules. I'm pretty confident Fluent allows chaining so I'm not sure what's going wrong here?

Upvotes: 0

Views: 2325

Answers (1)

rgvlee
rgvlee

Reputation: 3183

Default FV behaviour is to run through all of the rules for the property. If you want to stop when it hits the first failure you'll need to do something like set the cascade mode.

It can be done at a few places depending on how you want the behaviour applied:

  • Somewhere early in your app using the static options (ValidatorOptions.Global.CascadeMode = CascadeMode.Stop;)
  • In the validator using CascadeMode = CascadeMode.Stop;
  • On the rule using RuleFor(r => r.Feeling).Cascade(CascadeMode.Stop).ValidateFeeling();

Your extension bundles 2 rules, you could specify the cascade there but you'd need to use the IRuleBuilderInitial interface. I suspect that'd mean that you couldn't chain that rule with other rules in the validator for the property unless it was the first rule. Probably for the best anyway as it'd obfuscate the cascade mode from the validator which has a certain smell to it.

Upvotes: 1

Related Questions