FluentValidation multiple validators
Asked Answered
F

5

19

Can I add more than one validator to an object? For example:

public interface IFoo
{
    int Id { get; set; }
    string Name { get; set; }
}

public interface IBar
{
    string Stuff { get; set; }
}

public class FooValidator : AbstractValidator<IFoo>
{
    public FooValidator ()
    {
        RuleFor(x => x.Id).NotEmpty().GreaterThan(0);
    }
}

public class BarValidator : AbstractValidator<IBar>
{
    public BarValidator()
    {
        RuleFor(x => x.Stuff).Length(5, 30);
    }
}

public class FooBar : IFoo, IBar
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Stuff { get; set; }
}

public class FooBarValidator : AbstractValidator<FooBar>
{
    public FooBarValidator()
    {
        RuleFor(x => x)
            .SetValidator(new FooValidator())
            .SetValidator(new BarValidator());
    }
}

Running the test.

FooBarValidator validator = new FooBarValidator();
validator.ShouldHaveValidationErrorFor(x => x.Id, 0);

I get an InvalidOperationException:

Property name could not be automatically determined for expression x => x. Please specify either a custom property name by calling 'WithName'.

Is there any way to implement this or am I trying to use FluentValidation in a way that it's not meant to be used?

Figwort answered 2/11, 2012 at 15:38 Comment(0)
D
25

RuleFor is trying to create a property-level rule. You can additionally use the AddRule function to add a general-purpose rule.

Using this, I created a composite rule proof of concept. It takes in a set of other validators and runs them. The yield break code came straight from FluentValidator's DelegateValidator. I wasn't sure what to do with it so I grabbed that from the source. I didn't trace its full purpose, but everything seems to work as is :)

Code

public interface IFoo
{
    int Id { get; set; }
    string Name { get; set; }
}

public interface IBar
{
    string Stuff { get; set; }
}

public class FooValidator : AbstractValidator<IFoo>
{
    public FooValidator()
    {
        RuleFor(x => x.Id).NotEmpty().GreaterThan(0);
    }
}

public class BarValidator : AbstractValidator<IBar>
{
    public BarValidator()
    {
        RuleFor(x => x.Stuff).Length(5, 30);
    }
}

public class FooBar : IFoo, IBar
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Stuff { get; set; }
}

public class CompositeValidatorRule : IValidationRule
{
    private IValidator[] _validators;

    public CompositeValidatorRule(params IValidator[] validators)
    {
        _validators = validators;
    }

    #region IValidationRule Members
    public string RuleSet
    {
        get; set;
    }

    public IEnumerable<ServiceStack.FluentValidation.Results.ValidationFailure> Validate(ValidationContext context)
    {
        var ret = new List<ServiceStack.FluentValidation.Results.ValidationFailure>();

        foreach(var v in _validators)
        {
            ret.AddRange(v.Validate(context).Errors);
        }

        return ret;
    }

    public IEnumerable<ServiceStack.FluentValidation.Validators.IPropertyValidator> Validators
    {
        get { yield break; }
    }
    #endregion
}

public class FooBarValidator : AbstractValidator<FooBar>
{
    public FooBarValidator()
    {
        AddRule(new CompositeValidatorRule(new FooValidator(), new BarValidator()));
    }
}

Base Test Case:

    [TestMethod]
    public void TestValidator()
    {
        FooBarValidator validator = new FooBarValidator();
        var result = validator.Validate(new FooBar());

    }

I hope this helps.

Dominicdominica answered 2/11, 2012 at 17:52 Comment(2)
With a little bit of reflection, you could auto-find all validators that a composite validator should wire up to (i.e. find all the interfaces and base class chains and see if there's an IValidator<T> for them and if so auto-add it to the list). Just depends how far you want to take it. I'd probably opt to use reflection to find all the validators instead of a manual list.Dominicdominica
This reply, while it still seems to work (with minor modifications) is quite old. Is there a built-in way to do this yet? Closest I could find was the "Include" method which has been added, but it only allows including IValidators of the same type as the one doing the Include, so can't be used for multiple interfaces as per the example above.Stanwood
J
9

"Including Rules. You can include rules from other validators provided they validate the same type."

public class PersonValidator : AbstractValidator<Person> {
    public PersonValidator() {
        Include(new PersonAgeValidator());
        Include(new PersonNameValidator());
    }
}

https://docs.fluentvalidation.net/en/latest/including-rules.html

Jobbery answered 22/9, 2020 at 7:8 Comment(0)
I
7

Another possibility would be to override Validate:

public override ValidationResult Validate(ValidationContext<FooBar> context)
{
    var fooResult = new FooValidator().Validate(context.InstanceToValidate);
    var barResult = new BarValidator().Validate(context.InstanceToValidate);

    var errors = new List<ValidationFailure>();
    errors.AddRange(fooResult.Errors);
    errors.AddRange(barResult.Errors);

    return new ValidationResult(errors);
}
Indign answered 20/2, 2017 at 23:16 Comment(0)
D
1

You could use RuleSets to apply different types of validation if that helps with what you are trying to do:

FluentValidation RuleSets

Denitrify answered 2/11, 2012 at 15:41 Comment(1)
RuleSets wont fit. I would always like to run all of the validation rules. I'm trying to build up DTO's that have a lot of the same properties so it would be nice to reuse the validators without having nested obj properties in the DTO.Hypostasis
B
0

I needed to be able to have separate independent validators where each validator only should validate a single property. I used dependency injection to inject a list of IValidator and have an extension method to validate using all validators in the list.

Extension

public static class FluentValidationExtensions
{
    public static ValidationResult Validate<T>(this IEnumerable<IValidator<T>> validators, T value)
    {
        var errors = validators
            .Select(validator => validator.Validate(value))
            .Where(result => !result.IsValid)
            .SelectMany(result => result.Errors);

        return new ValidationResult(errors);
    }
}

Inject list of validators

public class Foo
{
    private readonly IEnumerable<IValidator<Bar>> _barValidators;

    public Foo(IEnumerable<IValidator<Bar>> barValidators)
    {
        _barValidators = barValidators;
    }
}

Validate

var bar = new Bar();

var result = _barValidators.Validate(bar);
Becalmed answered 14/5, 2024 at 10:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.