SharpGrip FluentValidation AutoValidation

repository·master·Indexed 42 days ago

https://github.com/sharpgrip/fluentvalidation.autovalidation

An extension for FluentValidation (v10+) providing automatic, asynchronous validation for ASP.NET Core MVC controllers and Minimal APIs. It serves as a modern replacement for the deprecated FluentValidation.AspNetCore library, featuring support for validation interceptors, custom result factories, and granular control via attributes like [AutoValidateAlways] and [AutoValidateNever].

Tokens
5.3K
Snippets
12
Records
16
Agent score
3%

What's inside SharpGrip FluentValidation AutoValidation

  1. Introduction to SharpGrip FluentValidation AutoValidation

    master

    SharpGrip FluentValidation AutoValidation is an extension for FluentValidation (v10+) that enables automatic asynchronous validation in both MVC controllers and Minimal APIs (endpoints).

    It serves as a modern replacement for the unmaintained FluentValidation.AspNetCore library, re-introducing automatic validation for MVC and providing new support for Minimal APIs.

  2. How validation interceptors work

    master

    Validation interceptors allow you to intercept and modify the validation process. There are two types of interceptors that form a pipeline:

    1. Per-validator interceptor: Implemented via IValidatorInterceptor on a specific validator.
    2. Global validation interceptor: Implemented via IGlobalValidationInterceptor and registered with the service provider.

    The Interceptor Pipeline

    The execution order for both BeforeValidation and AfterValidation follows this sequence:

    1. IValidatorInterceptor.BeforeValidation()
    2. IGlobalValidationInterceptor.BeforeValidation()
    3. >> Validation occurs >>
    4. IValidatorInterceptor.AfterValidation()
    5. IGlobalValidationInterceptor.AfterValidation()

    Interceptor Methods

    • BeforeValidation: Allows you to return a custom IValidationContext. If you return null, the default IValidationContext is used.
    • AfterValidation: Allows you to return a custom IValidationResult. If you return null, the existing ValidationResult is passed to the IFluentValidationAutoValidationResultFactory.
  3. Register FluentValidation validators in DI

    master

    Before enabling auto-validation, you must register your validators with the Dependency Injection (DI) service container so the library can resolve them. You can register them manually or automatically via assembly scanning.

    // Manual registration
    services.AddScoped<IValidator<User>, UserValidator>();
    
    // Automatic registration (scans assembly containing UserValidator)
    services.AddValidatorsFromAssemblyContaining<UserValidator>();
  4. Enable automatic validation for MVC controllers

    master

    To enable automatic validation for MVC controllers, install the SharpGrip.FluentValidation.AutoValidation.Mvc NuGet package and call AddFluentValidationAutoValidation() in your service configuration.

    using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
    
    builder.Services.AddFluentValidationAutoValidation();
  5. Migrate MVC controller configuration from v1 to v2

    master

    When upgrading from v1 to v2, note that the default values for the following configuration options have changed from false to true. If your application relies on these being disabled, you must explicitly set them to false in your configuration:

    • EnableFormBindingSourceAutomaticValidation
    • EnablePathBindingSourceAutomaticValidation
  6. Enable automatic validation for Minimal APIs (endpoints)

    master

    To enable automatic validation for Minimal APIs, install the SharpGrip.FluentValidation.AutoValidation.Endpoints NuGet package. You can enable validation globally for the service container, or specifically for individual routes or route groups using the .AddFluentValidationAutoValidation() extension method.

    using SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions;
    
    builder.Services.AddFluentValidationAutoValidation();
    
    var app = builder.Build();
    
    // Enable for a specific group
    var endpointGroup = app.MapGroup("/some-group").AddFluentValidationAutoValidation();
    endpointGroup.MapPost("/", (SomeModel someModel) => $"Hello {someModel.Name}");
    
    // Enable for a specific route
    app.MapPost("/", (SomeOtherModel someOtherModel) => $"Hello again {someOtherModel.Name}").AddFluentValidationAutoValidation();
  7. Configure Minimal API AutoValidation settings

    master

    For Minimal APIs, you can customize the validation behavior by providing a configuration delegate to AddFluentValidationAutoValidation. The primary customization is overriding the default result factory.

    using SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions;
    
    builder.Services.AddFluentValidationAutoValidation(configuration =>
    {
        configuration.OverrideDefaultResultFactoryWith<CustomResultFactory>();
    });
    
    public class CustomResultFactory : IFluentValidationAutoValidationResultFactory
    {
        public IResult CreateResult(EndpointFilterInvocationContext context, ValidationResult validationResult)
        {
            var validationProblemErrors = validationResult.ToValidationProblemErrors();
            return Results.ValidationProblem(validationProblemErrors, "Some details text.", "Some instance text.", (int) HttpStatusCode.BadRequest, "Some title.");
        }
    }
  8. Configure MVC AutoValidation settings

    master

    You can customize how MVC auto-validation behaves using the configuration delegate in AddFluentValidationAutoValidation.

    Key configuration options include:

    • DisableBuiltInModelValidation: Set to true to disable standard .NET data annotations validation.
    • ValidationStrategy: Determines when validation occurs. ValidationStrategy.All validates all controllers. ValidationStrategy.Annotations only validates controllers or methods decorated with the [AutoValidationAttribute].
    • Enable[Source]BindingSourceAutomaticValidation: Toggles validation for specific binding sources (Body, Form, Query, Path, Header, Custom, or Null).
    using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
    
    builder.Services.AddFluentValidationAutoValidation(configuration =>
    {
        configuration.DisableBuiltInModelValidation = true;
        configuration.ValidationStrategy = ValidationStrategy.Annotations;
        configuration.EnableBodyBindingSourceAutomaticValidation = true;
        configuration.EnableFormBindingSourceAutomaticValidation = true;
        configuration.EnableQueryBindingSourceAutomaticValidation = true;
        configuration.EnablePathBindingSourceAutomaticValidation = true;
        configuration.EnableCustomBindingSourceAutomaticValidation = true;
    
        // Replace the default result factory with a custom implementation
        configuration.OverrideDefaultResultFactoryWith<CustomResultFactory>();
    });
    
    public class CustomResultFactory : IFluentValidationAutoValidationResultFactory
    {
        public Task<IActionResult?> CreateActionResult(ActionExecutingContext context, ValidationProblemDetails validationProblemDetails, IDictionary<IValidationContext, ValidationResult> validationResults)
        {
            return new BadRequestObjectResult(new { Title = "Validation errors", ValidationErrors = validationProblemDetails?.Errors });
        }
    }
  9. Implement a global validation interceptor for Minimal APIs

    master

    For Minimal APIs, implement IGlobalValidationInterceptor. The method signatures differ from MVC, using EndpointFilterInvocationContext instead of ActionExecutingContext.

    // Registration
    builder.Services.AddTransient<IGlobalValidationInterceptor, CustomGlobalValidationInterceptor>();
    
    public class CustomGlobalValidationInterceptor : IGlobalValidationInterceptor
    {
        public Task<IValidationContext?> BeforeValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, CancellationToken cancellationToken = default)
        {
            // Return a custom `IValidationContext` or null.
            return Task.FromResult<IValidationContext?>(null);
        }
    
        public Task<ValidationResult?> AfterValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default)
        {
            // Return a custom `ValidationResult` or null.
            return Task.FromResult<ValidationResult?>(null);
        }
    }
  10. Implement a global validation interceptor for MVC

    master

    To create a global interceptor for MVC controllers, implement IGlobalValidationInterceptor and register it as a transient service in your dependency injection container.

    Note that the method signatures for MVC use ActionExecutingContext.

    // Registration
    builder.Services.AddTransient<IGlobalValidationInterceptor, CustomGlobalValidationInterceptor>();
    
    public class CustomGlobalValidationInterceptor : IGlobalValidationInterceptor
    {
        public Task<IValidationContext?> BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, CancellationToken cancellationToken = default)
        {
            // Return a custom `IValidationContext` or null.
            return Task.FromResult<IValidationContext?>(null);
        }
    
        public Task<ValidationResult?> AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default)
        {
            // Return a custom `ValidationResult` or null.
            return Task.FromResult<ValidationResult?>(null);
        }
    }
  11. Implement a per-validator interceptor for Minimal APIs

    master

    To intercept validation for a specific model in Minimal APIs, implement IValidatorInterceptor on your validator class using the EndpointFilterInvocationContext signature.

    private class TestValidator : AbstractValidator<TestModel>, IValidatorInterceptor
    {
        public TestValidator()
        {
            RuleFor(x => x.Parameter1).Empty();
            RuleFor(x => x.Parameter2).Empty();
            RuleFor(x => x.Parameter3).Empty();
        }
    
        public Task<IValidationContext?> BeforeValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, CancellationToken cancellationToken = default)
        {
            return Task.FromResult<IValidationContext?>(null);
        }
    
        public Task<ValidationResult?> AfterValidation(EndpointFilterInvocationContext endpointFilterInvocationContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default)
        {
            return Task.FromResult<ValidationResult?>(null);
        }
    }
  12. Implement a per-validator interceptor for MVC

    master

    To intercept validation for a specific model, implement the IValidatorInterceptor interface directly on your AbstractValidator<T> class.

    private class TestValidator : AbstractValidator<TestModel>, IValidatorInterceptor
    {
        public TestValidator()
        {
            RuleFor(x => x.Parameter1).Empty();
            RuleFor(x => x.Parameter2).Empty();
            RuleFor(x => x.Parameter3).Empty();
        }
    
        public Task<IValidationContext?> BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, CancellationToken cancellationToken = default)
        {
            return Task.FromResult<IValidationContext?>(null);
        }
    
        public Task<ValidationResult?> AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext, ValidationResult validationResult, CancellationToken cancellationToken = default)
        {
            return Task.FromResult<ValidationResult?>(null);
        }
    }