DEV Community

Alex
Alex

Posted on

.NET Learning Notes: Auto Parameters Validator

Though FluentValidation.AspNetCore is not maintained, we can still integrate FluentValidation with a custom action filter to achieve automatic validation.

1.Install FluentValidation NuGet Package

dotnet add package FluentValidation
Enter fullscreen mode Exit fullscreen mode

2.Create Your Validator Classes

public record UpdateCategoryRequest(long CategoryId, string? Name, string? Description);

public class UpdateCategoryRequestValidator : AbstractValidator<UpdateCategoryRequest>
{
    public UpdateCategoryRequestValidator()
    {
        RuleFor(x => x.CategoryId).NotEmpty();
        RuleFor(x => x)
            .Must(x => !string.IsNullOrWhiteSpace(x.Name)
            || !string.IsNullOrWhiteSpace(x.Description))
            .WithMessage("Either Name or Description must be provided");
    }
}
Enter fullscreen mode Exit fullscreen mode

3.Create a Custom Action Filter for Validation

public class ValidationFilter(IServiceProvider serviceProvider) : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        foreach (var arg in context.ActionArguments)
        {
            var argumentType = arg.Value?.GetType();
            if (argumentType == null) continue;
            // Create a type, for example, Class A -> IValidator<A>
            var validatorType = typeof(IValidator<>).MakeGenericType(argumentType);
            var validator = serviceProvider.GetService(validatorType);
            if (validator is IValidator val)
            {
                // Invoke Validate method
                var validationResult = await val.ValidateAsync(new ValidationContext<object>(arg.Value!));
                if (!validationResult.IsValid)
                {
                    var errors = validationResult.Errors.Select(x => x.ErrorMessage).ToList();
                    context.Result = new BadRequestObjectResult(
                        new ResponseData(ApiResponseCode.ParameterError, errors));
                    return;
                }
            }
        }
        await next();
    }
}
Enter fullscreen mode Exit fullscreen mode

4.Register the Validator and ActionFilter

builder.Services.AddValidatorsFromAssemblyContaining<RegisterRequestValidator>();
builder.Services.AddControllers(options =>
{
    options.Filters.Add<ApiResponseWrapperFilter>(order: 1);
    options.Filters.Add<JwtVersionCheckFilter>(order: 2);
    options.Filters.Add<ValidationFilter>(order: 3);
});
Enter fullscreen mode Exit fullscreen mode

All Done!

Why do not we pass context when we invoke the validator.Valiadte() method?
There is a extension method to automatic create context. FluentValidation assumes the context to be the instance of the object being validated and automatically creates the necessary ValidationContext when you call validator.Validate(instance). This is the default and most common use case.

public static ValidationResult Validate<T>(this IValidator<T> validator, T instance, Action<ValidationStrategy<T>> options)
        => validator.Validate(ValidationContext<T>.CreateWithOptions(instance, options));
Enter fullscreen mode Exit fullscreen mode

Top comments (0)