diff --git a/FluentValidation.AutoValidation.Endpoints/FluentValidation.AutoValidation.Endpoints.csproj b/FluentValidation.AutoValidation.Endpoints/FluentValidation.AutoValidation.Endpoints.csproj index a98226e..b7e579f 100644 --- a/FluentValidation.AutoValidation.Endpoints/FluentValidation.AutoValidation.Endpoints.csproj +++ b/FluentValidation.AutoValidation.Endpoints/FluentValidation.AutoValidation.Endpoints.csproj @@ -10,7 +10,7 @@ SharpGrip.FluentValidation.AutoValidation.Endpoints SharpGrip FluentValidation AutoValidation Endpoints SharpGrip FluentValidation AutoValidation Endpoints is an extension of the FluentValidation library enabling automatic asynchronous validation in minimal APIs (endpoints). - sharpgrip;validation;fluent-validation;minimal-api + sharpgrip;validation;fluent-validation;endpoints;minimal-api diff --git a/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs b/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs new file mode 100644 index 0000000..4e82152 --- /dev/null +++ b/FluentValidation.AutoValidation.Endpoints/src/Configuration/AutoValidationEndpointsConfiguration.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.AspNetCore.Http.HttpResults; +using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results; + +namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Configuration +{ + public class AutoValidationEndpointsConfiguration + { + /// + /// Holds the overridden result factory. This property is meant for infrastructure and should not be used by application code. + /// + public Type? OverriddenResultFactory { get; private set; } + + /// + /// Overrides the default result factory with a custom result factory. Custom result factories are required to implement . + /// The default result factory returns the validation errors wrapped in a object. + /// + /// + /// The custom result factory implementing . + public void OverrideDefaultResultFactoryWith() where TResultFactory : IFluentValidationAutoValidationResultFactory + { + OverriddenResultFactory = typeof(TResultFactory); + } + } +} \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Endpoints/src/Extensions/EndpointRouteExtensions.cs b/FluentValidation.AutoValidation.Endpoints/src/Extensions/EndpointRouteExtensions.cs index 59401fa..e6f6fbf 100644 --- a/FluentValidation.AutoValidation.Endpoints/src/Extensions/EndpointRouteExtensions.cs +++ b/FluentValidation.AutoValidation.Endpoints/src/Extensions/EndpointRouteExtensions.cs @@ -8,7 +8,7 @@ namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions public static class EndpointRouteExtensions { /// - /// Adds asynchronous minimal API automatic validation to the specified . + /// Adds asynchronous minimal API automatic validation to the specified . /// /// The route handler builder. /// The route handler builder. @@ -20,7 +20,7 @@ public static RouteHandlerBuilder AddFluentValidationAutoValidation(this RouteHa } /// - /// Adds asynchronous minimal API Fluent Validation automatic validation to the specified . + /// Adds asynchronous minimal API Fluent Validation automatic validation to the specified . /// /// The route group builder. /// The route group builder. diff --git a/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs b/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f4b0a82 --- /dev/null +++ b/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using SharpGrip.FluentValidation.AutoValidation.Endpoints.Configuration; +using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results; + +namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions +{ + public static class ServiceCollectionExtensions + { + /// + /// Adds asynchronous Endpoints Fluent Validation automatic validation to the specified . + /// + /// The service collection. + /// The configuration delegate used to configure the FluentValidation AutoValidation Endpoints validation. + /// The service collection. + public static IServiceCollection AddFluentValidationAutoValidation(this IServiceCollection serviceCollection, + Action? autoValidationEndpointsConfiguration = null) + { + var configuration = new AutoValidationEndpointsConfiguration(); + + if (autoValidationEndpointsConfiguration != null) + { + autoValidationEndpointsConfiguration.Invoke(configuration); + serviceCollection.Configure(autoValidationEndpointsConfiguration); + } + + // Add the default result factory. + serviceCollection.AddScoped(); + + // If the custom result factory is not null, replace the default result factory with the overridden result factory. + if (configuration.OverriddenResultFactory != null) + { + serviceCollection.Replace(new ServiceDescriptor(typeof(IFluentValidationAutoValidationResultFactory), configuration.OverriddenResultFactory, ServiceLifetime.Scoped)); + } + + return serviceCollection; + } + } +} \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs b/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs index 574b650..b8d79c0 100644 --- a/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs +++ b/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using FluentValidation; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results; using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions; namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Filters @@ -29,14 +29,14 @@ public FluentValidationAutoValidationEndpointFilter(IServiceProvider serviceProv if (!validationResult.IsValid) { - var errors = new Dictionary(); + var fluentValidationAutoValidationResultFactory = serviceProvider.GetService(); - foreach (var errorGrouping in validationResult.Errors.GroupBy(error => error.PropertyName)) + if (fluentValidationAutoValidationResultFactory != null) { - errors.Add(errorGrouping.Key, errorGrouping.Select(error => error.ErrorMessage).ToArray()); + return fluentValidationAutoValidationResultFactory.CreateResult(context, validationResult); } - return Results.ValidationProblem(errors); + return new FluentValidationAutoValidationDefaultResultFactory().CreateResult(context, validationResult); } } } diff --git a/FluentValidation.AutoValidation.Endpoints/src/Results/FluentValidationAutoValidationDefaultResultFactory.cs b/FluentValidation.AutoValidation.Endpoints/src/Results/FluentValidationAutoValidationDefaultResultFactory.cs new file mode 100644 index 0000000..30626be --- /dev/null +++ b/FluentValidation.AutoValidation.Endpoints/src/Results/FluentValidationAutoValidationDefaultResultFactory.cs @@ -0,0 +1,14 @@ +using FluentValidation.Results; +using Microsoft.AspNetCore.Http; +using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions; + +namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Results +{ + public class FluentValidationAutoValidationDefaultResultFactory : IFluentValidationAutoValidationResultFactory + { + public IResult CreateResult(EndpointFilterInvocationContext context, ValidationResult validationResult) + { + return TypedResults.ValidationProblem(validationResult.ToValidationProblemErrors()); + } + } +} \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Endpoints/src/Results/IFluentValidationAutoValidationResultFactory.cs b/FluentValidation.AutoValidation.Endpoints/src/Results/IFluentValidationAutoValidationResultFactory.cs new file mode 100644 index 0000000..7cf4c99 --- /dev/null +++ b/FluentValidation.AutoValidation.Endpoints/src/Results/IFluentValidationAutoValidationResultFactory.cs @@ -0,0 +1,16 @@ +using FluentValidation.Results; +using Microsoft.AspNetCore.Http; + +namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Results +{ + public interface IFluentValidationAutoValidationResultFactory + { + /// + /// Creates an object to be executed by the endpoint. + /// + /// The associated with the current request/response. + /// The object containing the validation failures. + /// The object to be executed by the endpoint. + public IResult CreateResult(EndpointFilterInvocationContext context, ValidationResult validationResult); + } +} \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs b/FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs index 63ff84d..056e341 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Configuration/AutoValidationMvcConfiguration.cs @@ -1,18 +1,38 @@ -using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums; +using System; +using Microsoft.AspNetCore.Mvc; +using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes; +using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums; +using SharpGrip.FluentValidation.AutoValidation.Mvc.Results; namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration { public class AutoValidationMvcConfiguration { /// - /// Disables the built-in model validation. + /// Disables the built-in .NET model (data annotations) validation. /// public bool DisableBuiltInModelValidation { get; set; } /// - /// Configures the validation strategy. Validation strategy enables asynchronous automatic validation on all controllers inheriting from . - /// Validation strategy enables asynchronous automatic validation on controllers inheriting from decorated (class or method) with a attribute. + /// Configures the validation strategy. Validation strategy enables asynchronous automatic validation on all controllers inheriting from . + /// Validation strategy enables asynchronous automatic validation on controllers inheriting from decorated (class or method) with a attribute. /// public ValidationStrategy ValidationStrategy { get; set; } = ValidationStrategy.All; + + /// + /// Holds the overridden result factory. This property is meant for infrastructure and should not be used by application code. + /// + public Type? OverriddenResultFactory { get; private set; } + + /// + /// Overrides the default result factory with a custom result factory. Custom result factories are required to implement . + /// The default result factory returns the default object wrapped in a >. + /// + /// + /// The custom result factory implement . + public void OverrideDefaultResultFactoryWith() where TResultFactory : IFluentValidationAutoValidationResultFactory + { + OverriddenResultFactory = typeof(TResultFactory); + } } } \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Enums/ValidationStrategy.cs b/FluentValidation.AutoValidation.Mvc/src/Enums/ValidationStrategy.cs index 88eb7a2..afdd392 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Enums/ValidationStrategy.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Enums/ValidationStrategy.cs @@ -1,14 +1,17 @@ -namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Enums +using Microsoft.AspNetCore.Mvc; +using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes; + +namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Enums { public enum ValidationStrategy { /// - /// Enables asynchronous automatic validation on all controllers inheriting from . + /// Enables asynchronous automatic validation on all controllers inheriting from . /// All = 1, /// - /// Enables asynchronous automatic validation on controllers inheriting from decorated with a attribute. + /// Enables asynchronous automatic validation on controllers inheriting from decorated with a attribute. /// Annotations = 2 } diff --git a/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs b/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs index dcaf3cd..9f2cc79 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs @@ -4,10 +4,12 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration; using SharpGrip.FluentValidation.AutoValidation.Mvc.Filters; +using SharpGrip.FluentValidation.AutoValidation.Mvc.Results; using SharpGrip.FluentValidation.AutoValidation.Mvc.Validation; namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions @@ -15,7 +17,7 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions public static class ServiceCollectionExtensions { /// - /// Adds asynchronous MVC Fluent Validation automatic validation to the specified . + /// Adds asynchronous MVC Fluent Validation automatic validation to the specified . /// /// The service collection. /// The configuration delegate used to configure the FluentValidation AutoValidation MVC validation. @@ -39,6 +41,15 @@ public static IServiceCollection AddFluentValidationAutoValidation(this IService configuration.DisableBuiltInModelValidation)); } + // Add the default result factory. + serviceCollection.AddScoped(); + + // If the custom result factory is not null, replace the default result factory with the overridden result factory. + if (configuration.OverriddenResultFactory != null) + { + serviceCollection.Replace(new ServiceDescriptor(typeof(IFluentValidationAutoValidationResultFactory), configuration.OverriddenResultFactory, ServiceLifetime.Scoped)); + } + // Create a default instance of the `ModelStateInvalidFilter` to access the non static property `Order` in a static context. var modelStateInvalidFilter = new ModelStateInvalidFilter(new ApiBehaviorOptions {InvalidModelStateResponseFactory = context => new OkResult()}, NullLogger.Instance); diff --git a/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs b/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs index f3741ba..964cce0 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs @@ -4,12 +4,14 @@ using FluentValidation; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Options; using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes; using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration; using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums; +using SharpGrip.FluentValidation.AutoValidation.Mvc.Results; using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions; namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Filters @@ -17,11 +19,15 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Filters public class FluentValidationAutoValidationActionFilter : IAsyncActionFilter { private readonly IServiceProvider serviceProvider; + private readonly IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory; private readonly AutoValidationMvcConfiguration autoValidationMvcConfiguration; - public FluentValidationAutoValidationActionFilter(IServiceProvider serviceProvider, IOptions autoValidationMvcConfiguration) + public FluentValidationAutoValidationActionFilter(IServiceProvider serviceProvider, + IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory, + IOptions autoValidationMvcConfiguration) { this.serviceProvider = serviceProvider; + this.fluentValidationAutoValidationResultFactory = fluentValidationAutoValidationResultFactory; this.autoValidationMvcConfiguration = autoValidationMvcConfiguration.Value; } @@ -29,8 +35,8 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE { if (context.Controller is ControllerBase controllerBase) { - var actionDescriptor = context.ActionDescriptor; var endpoint = context.HttpContext.GetEndpoint(); + var controllerActionDescriptor = (ControllerActionDescriptor) context.ActionDescriptor; if (autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotations && !(endpoint?.Metadata.OfType().Any() ?? false)) { @@ -39,16 +45,13 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE return; } - foreach (var parameter in actionDescriptor.Parameters) + foreach (var parameter in controllerActionDescriptor.Parameters) { if (context.ActionArguments.TryGetValue(parameter.Name, out var subject)) { var parameterType = parameter.ParameterType; - - // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract var bindingSource = parameter.BindingInfo?.BindingSource; - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (subject != null && (bindingSource == BindingSource.Body || (bindingSource == BindingSource.Query && parameterType.IsClass))) { if (serviceProvider.GetValidator(parameterType) is IValidator validator) @@ -69,9 +72,9 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE if (!context.ModelState.IsValid) { - var validationProblem = controllerBase.ProblemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState); + var validationProblemDetails = controllerBase.ProblemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState); - context.Result = new BadRequestObjectResult(validationProblem); + context.Result = fluentValidationAutoValidationResultFactory.CreateActionResult(context, validationProblemDetails); return; } diff --git a/FluentValidation.AutoValidation.Mvc/src/Results/FluentValidationAutoValidationDefaultResultFactory.cs b/FluentValidation.AutoValidation.Mvc/src/Results/FluentValidationAutoValidationDefaultResultFactory.cs new file mode 100644 index 0000000..bef32b9 --- /dev/null +++ b/FluentValidation.AutoValidation.Mvc/src/Results/FluentValidationAutoValidationDefaultResultFactory.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Results +{ + public class FluentValidationAutoValidationDefaultResultFactory : IFluentValidationAutoValidationResultFactory + { + public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails) + { + return new BadRequestObjectResult(validationProblemDetails); + } + } +} \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Results/IFluentValidationAutoValidationResultFactory.cs b/FluentValidation.AutoValidation.Mvc/src/Results/IFluentValidationAutoValidationResultFactory.cs new file mode 100644 index 0000000..a106f1a --- /dev/null +++ b/FluentValidation.AutoValidation.Mvc/src/Results/IFluentValidationAutoValidationResultFactory.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Results +{ + public interface IFluentValidationAutoValidationResultFactory + { + /// + /// Creates a to be executed by the controller action. + /// + /// The associated with the current request/response. + /// The instance object containing the validation failures. + /// The object to be executed by the controller action. + public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails); + } +} \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationObjectModelValidator.cs b/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationObjectModelValidator.cs similarity index 100% rename from FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationObjectModelValidator.cs rename to FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationObjectModelValidator.cs diff --git a/FluentValidation.AutoValidation.Shared/FluentValidation.AutoValidation.Shared.csproj b/FluentValidation.AutoValidation.Shared/FluentValidation.AutoValidation.Shared.csproj index 138f731..7b86d38 100644 --- a/FluentValidation.AutoValidation.Shared/FluentValidation.AutoValidation.Shared.csproj +++ b/FluentValidation.AutoValidation.Shared/FluentValidation.AutoValidation.Shared.csproj @@ -10,7 +10,7 @@ SharpGrip.FluentValidation.AutoValidation.Shared SharpGrip FluentValidation AutoValidation Shared SharpGrip FluentValidation AutoValidation is an extension of the FluentValidation library enabling automatic asynchronous validation in MVC controllers and minimal APIs (endpoints). - sharpgrip;validation;fluent-validation;mvc;minimal-api + sharpgrip;validation;fluent-validation;mvc;endpoints;minimal-api diff --git a/FluentValidation.AutoValidation.Shared/src/Extensions/ValidationResultExtensions.cs b/FluentValidation.AutoValidation.Shared/src/Extensions/ValidationResultExtensions.cs new file mode 100644 index 0000000..1794022 --- /dev/null +++ b/FluentValidation.AutoValidation.Shared/src/Extensions/ValidationResultExtensions.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; + +namespace SharpGrip.FluentValidation.AutoValidation.Shared.Extensions +{ + public static class ValidationResultExtensions + { + public static IDictionary ToValidationProblemErrors(this ValidationResult validationResult) + { + return validationResult.Errors.GroupBy(validationFailure => validationFailure.PropertyName) + .ToDictionary(validationFailureGrouping => validationFailureGrouping.Key, + validationFailureGrouping => validationFailureGrouping.Select(validationFailure => validationFailure.ErrorMessage).ToArray()); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 8d063e8..fea0ed7 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,9 @@ ## Introduction -SharpGrip FluentValidation AutoValidation is an extension of the [FluentValidation](https://github.com/FluentValidation/FluentValidation) library enabling automatic asynchronous validation in MVC -controllers and minimal APIs (endpoints). -The library [FluentValidation.AspNetCore](https://github.com/FluentValidation/FluentValidation.AspNetCore) is no longer being maintained and is unsupported. As a result, support for automatic -validation provided by this library is no longer available. -This library re-introduces this functionality for MVC controllers and introduces automation validation for minimal APIs (endpoints). It enables developers to easily implement automatic validation in -their projects. +SharpGrip FluentValidation AutoValidation is an extension of the [FluentValidation](https://github.com/FluentValidation/FluentValidation) (v10+) library enabling automatic asynchronous validation in MVC controllers and minimal APIs (endpoints). +The library [FluentValidation.AspNetCore](https://github.com/FluentValidation/FluentValidation.AspNetCore) is no longer being maintained and is unsupported. As a result, support for automatic validation provided by this library is no longer available. +This library re-introduces this functionality for MVC controllers and introduces automation validation for minimal APIs (endpoints). It enables developers to easily implement automatic validation in their projects. ## Installation @@ -33,7 +30,7 @@ using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions; builder.Services.AddFluentValidationAutoValidation(); ``` -### Minimal APIs [![NuGet](https://img.shields.io/nuget/v/SharpGrip.FluentValidation.AutoValidation.Endpoints)](https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Endpoints) +### Minimal APIs (endpoints) [![NuGet](https://img.shields.io/nuget/v/SharpGrip.FluentValidation.AutoValidation.Endpoints)](https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Endpoints) For minimal APIs (endpoints) reference NuGet package `SharpGrip.FluentValidation.AutoValidation.Endpoints` (https://www.nuget.org/packages/SharpGrip.FluentValidation.AutoValidation.Endpoints). @@ -42,6 +39,9 @@ Enabling minimal API (endpoint) automatic validation can be done on both route g ``` using SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions; +builder.Services.AddFluentValidationAutoValidation(); + +var app = builder.Build(); var endpointGroup = app.MapGroup("/some-group").AddFluentValidationAutoValidation(); endpointGroup.MapPost("/", (SomeModel someModel) => $"Hello {someModel.Name}"); @@ -54,7 +54,7 @@ app.MapPost("/", (SomeOtherModel someOtherModel) => $"Hello again {someOtherMode | Property | Default value | Description | |-------------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| DisableBuiltInModelValidation | `false` | Disables the built-in model validation. | +| DisableBuiltInModelValidation | `false` | Disables the built-in .NET model (data annotations) validation. | | ValidationStrategy | `ValidationStrategy.All` | Configures the validation strategy. Validation strategy `ValidationStrategy.All` enables asynchronous automatic validation on all controllers inheriting from `ControllerBase`. Validation strategy `ValidationStrategy.Annotations` enables asynchronous automatic validation on controllers inheriting from `ControllerBase` decorated (class or method) with a `FluentValidationAutoValidationAttribute` attribute. | ``` @@ -62,9 +62,42 @@ using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions; builder.Services.AddFluentValidationAutoValidation(configuration => { - configuration.DisableDataAnnotationsValidation = true; + // Disable the built-in .NET model (data annotations) validation. + configuration.DisableBuiltInModelValidation = true; // Only validate controllers decorated with the `FluentValidationAutoValidation` attribute. configuration.ValidationStrategy = ValidationStrategy.Annotation; + + // Replace the default result factory with a custom implementation. + configuration.OverrideDefaultResultFactoryWith(); }); -``` \ No newline at end of file + +public class CustomResultFactory : IFluentValidationAutoValidationResultFactory +{ + public IActionResult CreateActionResult(ActionExecutingContext context, ValidationProblemDetails? validationProblemDetails) + { + return new BadRequestObjectResult(new {Title = "Validation errors", ValidationErrors = validationProblemDetails?.Errors}); + } +} +``` + +### Minimal APIs (endpoints) + +``` +using SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions; + +builder.Services.AddFluentValidationAutoValidation(configuration => +{ + // Replace the default result factory with a custom implementation. + configuration.OverrideDefaultResultFactoryWith(); +}); + +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."); + } +} +```