C# – Fluent Validation rules, subsets and nesting

asp.net-mvcc++fluentvalidationvalidation

Given a validator class that looks like this

 public class SomeValidator : AbstractValidator<SomeObject>
 {
      public SomeValidator(){
           RuleSet("First",
                () => {
                     RuleFor(so => so.SomeMember).SetValidator(new SomeMemberValidator())
           });
           RuleSet("Second",
                () => ... Code Does Not Matter ... );
           RuleSet("Third",
                () => ... Code Does Not Matter ... );
      }
 }

And another to do the inner member validation

 public class SomeMemberValidator: AbstractValidator<SomeMember>
 {
      public SomeValidator(){
           RuleSet("Fourth",
                () => {
                     ... Code Does Not Matter ...
           });
      }
 }

Question is, I want to run specific rulesets: "First", "Second", and "Fourth". I don't want "Third" to run.

Given the Validate method signature only takes a single ruleset argument I don't see any way to do this. There is "*", but I don't want to run all the rules.

Please help.

Best Solution

You could use validator constructor instead of RuleSet as a workaround for this problem. Just create enum inside of validator class and then use its value when creating validator.

I this way correct rules will be activated depending on what Mode is set in constructor.

public class UserValidator : AbstractValidator<User>
{
    public enum Mode
    {
        Create,
        Edit
    }

    public UserValidator()
    {
        // Default rules...
    }

    public UserValidator(UserValidator.Mode mode)
        : this()
    {
        if (mode == Mode.Edit)
        {
            // Rules for Edit...

            RuleFor(so => so.SomeMember)
                .SetValidator(
                    new SomeMemberValidator(SomeMemberValidator.Mode.SomeMode))
        }

        if (mode == Mode.Create)
        {
            // Rules for Create...

            RuleFor(so => so.SomeMember)
                .SetValidator(
                    new SomeMemberValidator())
        }
    }
}

I think it's actually more flexible method than using RuleSet.

There is only one small problem regarding FluentValidation MVC integration: User class can't have attribute [Validator(typeof(UserValidator))] because UserValidator will be then created using default constructor, before you can do anything in controller method.

Validator must be created and called manually. Like that for example:

public class UserController : Controller
{
    [HttpPost]
    public ActionResult Create(User userData)
    {
        var validator = new UserValidator(UserValidator.Mode.Create);

        if (ValidateWrapper(validator, userData, this.ModelState))
        {
            // Put userData in database...
        }
        else
        {
            // ValidateWrapper added errors from UserValidator to ModelState.
            return View();
        }
    }

    private static bool ValidateWrapper<T>(FluentValidation.AbstractValidator<T> validator, T data, ModelStateDictionary modelState)
    {
        var validationResult = validator.Validate(data);

        if (!validationResult.IsValid)
        {
            foreach (var error in validationResult.Errors)
                modelState.AddModelError(error.PropertyName, error.ErrorMessage);

            return false;
        }

        return true;
    }
}