21

I have a class with a complex property:

public class A
{
    public B Prop { get; set; }
}

public class B
{
    public int Id { get; set; }
}

I've added a validator:

public class AValidator : AbstractValidator<A>
{
    public AValidator()
    {
        RuleFor(x => x.A.Id)
            .NotEmpty()
            .WithMessage("Please ensure you have selected the A object");            
    }
}

But during client-side validation for A.Id I still have a default validation message: 'Id' must not be empty. How can I change it to my string from the validator?

Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
Roman Koliada
  • 4,286
  • 2
  • 30
  • 59
  • 1
    This appears to be [not supported](https://github.com/JeremySkinner/FluentValidation/issues/110). Suggested alternative approach is to have flat view models and avoid nesting of objects – Andrei Oct 10 '16 at 09:40
  • 1
    This is supported, and depends on where you want to use your validators. Checkout my answer below. – Maicon Heck Jul 09 '20 at 12:24

6 Answers6

25

You can achieve this by using custom validator for nested object:

public class AValidator : AbstractValidator<A>
{
    public AValidator()
    {
        RuleFor(x => x.B).NotNull().SetValidator(new BValidator());
    }

    class BValidator : AbstractValidator<B>
    {
        public BValidator()
        {
            RuleFor(x => x.Id).NotEmpty().WithMessage("Please ensure you have selected the B object");
        }
    }
}

public class A
{
    public B B { get; set; }
}

public class B
{
    public int Id { get; set; }
}
Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
10

There is an alternative option here. When configuring FluentValidation in your Startup class you can set the following configuration.ImplicitlyValidateChildProperties = true;

So the full code might look something like this

services
    .AddMvc()
    .AddFluentValidation(configuration =>
        {
            ...
            configuration.ImplicitlyValidateChildProperties = true;
            ...
        })

So you would still have two validators one for class A and one for class B, then class B would be validated.

The documentation states:

Whether or not child properties should be implicitly validated if a matching validator can be found. By default this is false, and you should wire up child validators using SetValidator.

So setting this to true implies that child properties will be validated.

Grigory Zhadko
  • 1,484
  • 1
  • 19
  • 33
Steven Yates
  • 2,400
  • 3
  • 30
  • 58
  • That's very interesting , would you happen to know how one would unit test this? – ErpaDerp Apr 12 '19 at 06:32
  • 1
    I would be interested how it can be incorporate with rulesets. How you can define the valid validation ruleset when your nested object will be validated? – AndrasCsanyi Dec 05 '19 at 12:30
  • this solution is now deprecated, see https://github.com/fluentvalidation/fluentvalidation/issues/1960 for more information. The solution now is to call `SetValidator` instead, see https://docs.fluentvalidation.net/en/latest/start.html#complex-properties – Zhiyuan-Amos Apr 06 '23 at 03:34
6

Pretty old question but for future generations - you can either use a child validator or define child rules inline as described in the official documentation: https://fluentvalidation.net/start#complex-properties

t138
  • 223
  • 1
  • 4
  • 7
3

It depends on where you want to use your validators:

In the common scenario, where we have an application with N layers, in the ASP.net layer, for the validation of View Models, DTOs or Commands (e.g. to achieve fail-fast validation), just enable ImplicitlyValidateChildProperties, as @StevenYates said:

services.AddMvc().AddFluentValidation(fv => 
{
    fv.ImplicitlyValidateChildProperties = true;
});

When this is enabled, instead of having to specify child validators using SetValidator, MVC’s validation infrastructure will recursively attempt to automatically find validators for each property automatically.

IMHO, this is great, because besides being practical, it prevents us from forgetting some .SetValidator (...)!

Note that if you enable this behaviour you should not use SetValidator for child properties, or the validator will be executed twice.

Doc: https://docs.fluentvalidation.net/en/latest/aspnet.html#implicit-vs-explicit-child-property-validation


But, in addition (or instead) of that, if you want to use FluentValidation in other layers (e.g. Domain), you need to use the SetValidator(...) method, as @t138 said, for example:

RuleFor(customer => customer.Address).SetValidator(new AddressValidator());

Doc: https://docs.fluentvalidation.net/en/latest/start.html#complex-properties

Maicon Heck
  • 2,017
  • 1
  • 20
  • 27
1

You can use ChildRules method:

public class AValidator : AbstractValidator<A>
{
    public AValidator()
    {
        RuleFor(x => x.Prop)
            .ChildRules(x => x.RuleFor(b => b.Id))
            .NotEmpty()
            .WithMessage("Please ensure you have selected the A object");
    }
}
0
public class A
{
    public B B { get; set; }
}

public class B
{
    public int Id { get; set; }
}

public class AValidator : AbstractValidator<A>
{
    public AValidator()
    {
        RuleFor(x => x.B).NotNull().SetValidator(new BValidator());
    }

    class BValidator : AbstractValidator<B>
    {
        public BValidator()
        {
            RuleFor(x => x.Id).NotEmpty().WithMessage("message ....");

        }
    }
}

----------------------------------------------------------------------
  public void ConfigureServices(IServiceCollection services)
   {
          
            services.AddControllers().AddFluentValidation(fv =>
            {
                fv.RegisterValidatorsFromAssemblyContaining<Startup>();
                fv.ImplicitlyValidateChildProperties = true;
            });
    }
Zaid Qassim
  • 109
  • 1
  • 5