FluentValidation implicit child validation using manual validation

Reading the documentation it appears that in .NET Core you can do implicit child properties validation with:

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

Unfortunately I'm not working with MVC directly so I have to call Validate myself (IValidator<T> instances are registered by myself with Scrutor). Is there a similar setting for manually validating nested fields as well as top-level fields (using .NET Core DI)?


Solution 1:

If you check the FluentValidation source code you will notice that the ImplicitlyValidateChildProperties option uses the Asp.Net mechanism of validating nested properties so it won't work for manual validation.

You can write a helper or extension method that uses reflection and the IValidatorFactory service to do nested validation:

public static async Task<ValidationResult> ValidateImplicitAsync<TReq>(this TReq request, IValidatorFactory factory)
            where TReq: class
{
    var result = new ValidationResult();
    await ValidateRecursively(request, factory, result);
    return result;
}

static async Task ValidateRecursively(
            object obj, 
            IValidatorFactory factory, 
            ValidationResult result)
{
    if(obj == null) { return; }
    Type t = obj.GetType();
    IValidator validator = factory.GetValidator(t);
    if(validator == null) { return; }
    ValidationResult r = await validator.ValidateAsync(new ValidationContext<object>(obj));
    foreach(var error in r.Errors)
    {
        result.Errors.Add(error);
    }
    foreach(var prop in t.GetProperties())
    {
        object childValue = prop.GetValue(obj, null);
        await ValidateRecursively(childValue, factory, result);
    }
}

Then you could call it like this:

public class MyService {
    readonly IValidatorFactory factory;
    
    public MyService(IValidatorFactory factory){
       this.factory = factory;
    }

    public async Task Handle(MyModel model){
       ValidationResult validationResult = await model.ValidateImplicitAsync(factory);
       // ... etc...
    }
}