How to await method in PropertyValidator's overridden IsValid?

Viewed 1415

I'm trying to use my Data Repository in PropertyValidator class. The problem is that method in the Repository is async... and I can't see IsValidAsync to override.

using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation.Validators;
using AccountApi.Domain.Repositories;

namespace AccountApi.Domain.Validators
{
    public class UserInputValidator<TElement> : PropertyValidator
    {
        private IUserRepository _userRepository;

        public UserInputValidator(IUserRepository userRepository)
            : base("{Message}.")
        {
            _userRepository = userRepository;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            var id = (int)context.PropertyValue;
            var user = await _userRepository.GetById(id); // <--- HERE is the problem  

            // ..... 

            return true;
        }
    }
} 

I tried to change to this:

protected override async Task<bool> IsValidAsync(PropertyValidatorContext context)   

... but it doesn't work as well. There is no IsValidAsync to override.

Is it possible to use async methods inside PropertyValidator?

3 Answers

You're sort of on the right track.

Yes, you do overload the PropertyValidator method protected virtual async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation)

However you need to use the validator.ValidateAsync(objectToValidate) method to use it.

If your validator contains asynchronous validators or asynchronous conditions, it’s important that you always call ValidateAsync on your validator and never Validate. If you call Validate, then your asynchronous rules will be run synchronously, which is not desirable.

Source

Additionally refer to the following issue where Jeremy suggests:

  • Also overloading the ShouldValidateAsync method; or better still
  • Use the AsyncValidatorBase rather than PropertyValidator which handles the ShouldValidateAsync overload for you

See the following if you want to see a working solution of an async property validator.

Is it possible to use async methods inside PropertyValidator?

You could press F12 on PropertyValidator in vs to see its methods.

public abstract class PropertyValidator : IPropertyValidator
{
    protected PropertyValidator(IStringSource errorMessageSource);
    protected PropertyValidator(string errorMessage);
    [Obsolete("This constructor will be removed in FluentValidation 9.0. Use the overload that takes an IStringSource instead, passing in a LazyStringSource: PropertyValidator(new LazyStringSource(ctx => MyResourceClass.MyResourceName))")]
    protected PropertyValidator(string errorMessageResourceName, Type errorMessageResourceType);

    public PropertyValidatorOptions Options { get; }

    public virtual bool ShouldValidateAsync(ValidationContext context);
    public virtual IEnumerable<ValidationFailure> Validate(PropertyValidatorContext context);
    [AsyncStateMachine(typeof(<ValidateAsync>d__7))]
    public virtual Task<IEnumerable<ValidationFailure>> ValidateAsync(PropertyValidatorContext context, CancellationToken cancellation);
    //
    // Summary:
    //     Creates an error validation result for this validator.
    //
    // Parameters:
    //   context:
    //     The validator context
    //
    // Returns:
    //     Returns an error validation result.
    protected virtual ValidationFailure CreateValidationError(PropertyValidatorContext context);
    protected abstract bool IsValid(PropertyValidatorContext context);
    [AsyncStateMachine(typeof(<IsValidAsync>d__10))]
    protected virtual Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation);
    //
    // Summary:
    //     Prepares the FluentValidation.Internal.MessageFormatter of context for an upcoming
    //     FluentValidation.Results.ValidationFailure.
    //
    // Parameters:
    //   context:
    //     The validator context
    protected virtual void PrepareMessageFormatterForValidationError(PropertyValidatorContext context);
}

So,try to use

protected override async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation)

It is simple. Just use "AsyncPropertyValidator" or "AsyncPropertyValidator<T, type>"

In your case it will be like

using System.Collections.Generic;
using System.Threading.Tasks;
using FluentValidation.Validators;
using AccountApi.Domain.Repositories;
    
namespace AccountApi.Domain.Validators
{
    public class UserInputValidator<TElement> : AsyncPropertyValidator
    {
        private IUserRepository _userRepository;

        public UserInputValidator(IUserRepository userRepository)
            : base("{Message}.")
        {
            _userRepository = userRepository;
        }

         public override async Task<bool> IsValidAsync(PropertyValidatorContext context)
        {
            var id = (int)context.PropertyValue;
            var user = await _userRepository.GetById(id);

            // .. 

            return true;
        }
    }
} 
Related