From f2ad59a1ea6e2315609860da04fff4964edac33e Mon Sep 17 00:00:00 2001 From: Mauro van der Gun Date: Wed, 4 Feb 2026 13:04:26 -0400 Subject: [PATCH] add logging --- .../Extensions/ServiceCollectionExtensions.cs | 3 +- ...tValidationAutoValidationEndpointFilter.cs | 19 ++++++- .../Attributes/AutoValidateAlwaysAttribute.cs | 4 +- .../Attributes/AutoValidateNeverAttribute.cs | 4 +- .../src/Attributes/AutoValidationAttribute.cs | 4 +- .../Extensions/ServiceCollectionExtensions.cs | 6 +- ...entValidationAutoValidationActionFilter.cs | 56 +++++++++++++++---- ...ationAutoValidationObjectModelValidator.cs | 16 +----- ...lidationAutoValidationValidationVisitor.cs | 23 +------- README.md | 23 ++++---- ...idationAutoValidationEndpointFilterTest.cs | 7 ++- ...alidationAutoValidationActionFilterTest.cs | 8 ++- 12 files changed, 95 insertions(+), 78 deletions(-) diff --git a/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs b/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs index f4b0a82..5d4250a 100644 --- a/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs +++ b/FluentValidation.AutoValidation.Endpoints/src/Extensions/ServiceCollectionExtensions.cs @@ -14,8 +14,7 @@ public static class ServiceCollectionExtensions /// 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) + public static IServiceCollection AddFluentValidationAutoValidation(this IServiceCollection serviceCollection, Action? autoValidationEndpointsConfiguration = null) { var configuration = new AutoValidationEndpointsConfiguration(); diff --git a/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs b/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs index 82bb295..5b9fa37 100644 --- a/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs +++ b/FluentValidation.AutoValidation.Endpoints/src/Filters/FluentValidationAutoValidationEndpointFilter.cs @@ -2,13 +2,14 @@ using FluentValidation; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using SharpGrip.FluentValidation.AutoValidation.Endpoints.Interceptors; using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results; using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions; namespace SharpGrip.FluentValidation.AutoValidation.Endpoints.Filters { - public class FluentValidationAutoValidationEndpointFilter : IEndpointFilter + public class FluentValidationAutoValidationEndpointFilter(ILogger logger) : IEndpointFilter { public async ValueTask InvokeAsync(EndpointFilterInvocationContext endpointFilterInvocationContext, EndpointFilterDelegate next) { @@ -18,6 +19,8 @@ public class FluentValidationAutoValidationEndpointFilter : IEndpointFilter { if (argument != null && argument.GetType().IsCustomType() && serviceProvider.GetValidator(argument.GetType()) is IValidator validator) { + logger.LogDebug("Starting validation for argument of type '{Type}'.", argument.GetType().Name); + var validatorInterceptor = validator as IValidatorInterceptor; var globalValidationInterceptor = serviceProvider.GetService(); @@ -25,11 +28,13 @@ public class FluentValidationAutoValidationEndpointFilter : IEndpointFilter if (validatorInterceptor != null) { + logger.LogDebug("Invoking validator interceptor BeforeValidation for argument '{Argument}'.", argument.GetType().Name); validationContext = await validatorInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext; } if (globalValidationInterceptor != null) { + logger.LogDebug("Invoking global validation interceptor BeforeValidation for argument '{Argument}'.", argument.GetType().Name); validationContext = await globalValidationInterceptor.BeforeValidation(endpointFilterInvocationContext, validationContext, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationContext; } @@ -37,25 +42,37 @@ public class FluentValidationAutoValidationEndpointFilter : IEndpointFilter if (validatorInterceptor != null) { + logger.LogDebug("Invoking validator interceptor AfterValidation for argument '{Argument}'.", argument.GetType().Name); validationResult = await validatorInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult; } if (globalValidationInterceptor != null) { + logger.LogDebug("Invoking global validation interceptor AfterValidation for argument '{Argument}'.", argument.GetType().Name); validationResult = await globalValidationInterceptor.AfterValidation(endpointFilterInvocationContext, validationContext, validationResult, endpointFilterInvocationContext.HttpContext.RequestAborted) ?? validationResult; } if (!validationResult.IsValid) { + logger.LogDebug("Validation result not valid for argument '{Argument}': {ErrorCount} validation errors found.", argument.GetType().Name, validationResult.Errors.Count); + var fluentValidationAutoValidationResultFactory = serviceProvider.GetService(); + logger.LogDebug("Creating result for path '{Path}'.", endpointFilterInvocationContext.HttpContext.Request.Path); + if (fluentValidationAutoValidationResultFactory != null) { + logger.LogTrace("Creating result for path '{Path}' using a custom result factory.", endpointFilterInvocationContext.HttpContext.Request.Path); + return fluentValidationAutoValidationResultFactory.CreateResult(endpointFilterInvocationContext, validationResult); } + logger.LogTrace("Creating result for path '{Path}' using the default result factory.", endpointFilterInvocationContext.HttpContext.Request.Path); + return new FluentValidationAutoValidationDefaultResultFactory().CreateResult(endpointFilterInvocationContext, validationResult); } + + logger.LogDebug("Validation result valid for argument '{Argument}'.", argument.GetType().Name); } } diff --git a/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateAlwaysAttribute.cs b/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateAlwaysAttribute.cs index 173d93d..c866642 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateAlwaysAttribute.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateAlwaysAttribute.cs @@ -3,7 +3,5 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes { [AttributeUsage(AttributeTargets.Parameter)] - public class AutoValidateAlwaysAttribute : Attribute - { - } + public class AutoValidateAlwaysAttribute : Attribute; } \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateNeverAttribute.cs b/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateNeverAttribute.cs index 186e61f..dce6a53 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateNeverAttribute.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidateNeverAttribute.cs @@ -3,7 +3,5 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter)] - public class AutoValidateNeverAttribute : Attribute - { - } + public class AutoValidateNeverAttribute : Attribute; } \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidationAttribute.cs b/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidationAttribute.cs index 799ba08..eaaf12c 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidationAttribute.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Attributes/AutoValidationAttribute.cs @@ -3,7 +3,5 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class AutoValidationAttribute : Attribute - { - } + public class AutoValidationAttribute : Attribute; } \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs b/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs index 5009139..1235b6b 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Extensions/ServiceCollectionExtensions.cs @@ -34,11 +34,7 @@ public static IServiceCollection AddFluentValidationAutoValidation(this IService if (configuration.DisableBuiltInModelValidation) { - serviceCollection.AddSingleton(serviceProvider => - new FluentValidationAutoValidationObjectModelValidator( - serviceProvider.GetRequiredService(), - serviceProvider.GetRequiredService>().Value.ModelValidatorProviders, - configuration.DisableBuiltInModelValidation)); + serviceCollection.AddSingleton(serviceProvider => new FluentValidationAutoValidationObjectModelValidator(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>().Value.ModelValidatorProviders, configuration.DisableBuiltInModelValidation)); } // Add the default result factory. diff --git a/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs b/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs index 1dfccd2..17714c5 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes; using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration; @@ -21,27 +22,25 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Filters { - public class FluentValidationAutoValidationActionFilter : IAsyncActionFilter + public class FluentValidationAutoValidationActionFilter(IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory, IOptions autoValidationMvcConfiguration, ILogger logger) : IAsyncActionFilter { - private readonly IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory; - private readonly AutoValidationMvcConfiguration autoValidationMvcConfiguration; - - public FluentValidationAutoValidationActionFilter(IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory, IOptions autoValidationMvcConfiguration) - { - this.fluentValidationAutoValidationResultFactory = fluentValidationAutoValidationResultFactory; - this.autoValidationMvcConfiguration = autoValidationMvcConfiguration.Value; - } + private readonly AutoValidationMvcConfiguration autoValidationMvcConfiguration = autoValidationMvcConfiguration.Value; public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingContext, ActionExecutionDelegate next) { + var controllerActionDescriptor = (ControllerActionDescriptor) actionExecutingContext.ActionDescriptor; + + logger.LogDebug("Starting validation for action '{Action}' on controller '{Controller}'.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + if (IsValidController(actionExecutingContext.Controller)) { var endpoint = actionExecutingContext.HttpContext.GetEndpoint(); - var controllerActionDescriptor = (ControllerActionDescriptor) actionExecutingContext.ActionDescriptor; var serviceProvider = actionExecutingContext.HttpContext.RequestServices; if (endpoint != null && ((autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotations && !endpoint.Metadata.OfType().Any()) || endpoint.Metadata.OfType().Any())) { + logger.LogDebug("Skipping validation for action '{Action}' on controller '{Controller}' due to validation strategy or AutoValidateNeverAttribute.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + HandleUnvalidatedEntries(actionExecutingContext); await next(); @@ -64,6 +63,8 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC if (subject != null && parameterType != null && parameterType.IsCustomType() && !hasAutoValidateNeverAttribute && (hasAutoValidateAlwaysAttribute || HasValidBindingSource(bindingSource)) && serviceProvider.GetValidator(parameterType) is IValidator validator) { + logger.LogDebug("Validating parameter '{Parameter}' of type '{Type}' for action '{Action}' on controller '{Controller}'.", parameter.Name, parameterType.Name, controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + // ReSharper disable once SuspiciousTypeConversion.Global var validatorInterceptor = validator as IValidatorInterceptor; var globalValidationInterceptor = serviceProvider.GetService(); @@ -72,11 +73,13 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC if (validatorInterceptor != null) { + logger.LogDebug("Invoking validator interceptor BeforeValidation for parameter '{Parameter}'.", parameter.Name); validationContext = await validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext; } if (globalValidationInterceptor != null) { + logger.LogDebug("Invoking global validation interceptor BeforeValidation for parameter '{Parameter}'.", parameter.Name); validationContext = await globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext; } @@ -85,21 +88,31 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC if (validatorInterceptor != null) { + logger.LogDebug("Invoking validator interceptor AfterValidation for parameter '{Parameter}'.", parameter.Name); validationResult = await validatorInterceptor.AfterValidation(actionExecutingContext, validationContext, validationResult) ?? validationResult; } if (globalValidationInterceptor != null) { + logger.LogDebug("Invoking global validation interceptor AfterValidation for parameter '{Parameter}'.", parameter.Name); validationResult = await globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext, validationResult) ?? validationResult; } if (!validationResult.IsValid) { + logger.LogDebug("Validation result not valid for parameter '{Parameter}' of type '{Type}' for action '{Action}' on controller '{Controller}': {ErrorCount} validation errors found.", parameter.Name, parameterType.Name, controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName, validationResult.Errors.Count); + foreach (var error in validationResult.Errors) { + logger.LogTrace("Adding validation error '{ErrorMessage}' for '{ParameterName}' to ModelState.", error.ErrorMessage, parameter.Name); + actionExecutingContext.ModelState.AddModelError(error.PropertyName, error.ErrorMessage); } } + else + { + logger.LogDebug("Validation result valid for parameter '{Parameter}' of type '{Type}' for action '{Action}' on controller '{Controller}'.", parameter.Name, parameterType.Name, controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + } } } } @@ -108,13 +121,28 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC if (!actionExecutingContext.ModelState.IsValid) { + logger.LogDebug("ModelState is not valid for action '{Action}' on controller '{Controller}'. Creating validation problem details.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + var problemDetailsFactory = serviceProvider.GetRequiredService(); var validationProblemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionExecutingContext.HttpContext, actionExecutingContext.ModelState); + logger.LogTrace("Creating action result for action '{Action}' on controller '{Controller}'.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + actionExecutingContext.Result = await fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails, validationResults); + if (actionExecutingContext.Result != null) + { + logger.LogTrace("Action result created for action '{Action}' on controller '{Controller}'.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + } + else + { + logger.LogTrace("No action result created for action '{Action}' on controller '{Controller}'.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); + } + return; } + + logger.LogDebug("ModelState is valid for action '{Action}' on controller '{Controller}'. Proceeding with action execution.", controllerActionDescriptor.ActionName, controllerActionDescriptor.ControllerName); } await next(); @@ -126,6 +154,8 @@ private bool IsValidController(object controller) if (controllerType.HasCustomAttribute()) { + logger.LogDebug("Controller '{Controller}' is marked with NonControllerAttribute. Skipping validation.", controllerType.Name); + return false; } @@ -147,11 +177,17 @@ private void HandleUnvalidatedEntries(ActionExecutingContext context) { if (autoValidationMvcConfiguration.DisableBuiltInModelValidation) { + logger.LogDebug("Skipping validation of unvalidated entries due to DisableBuiltInModelValidation being set to true."); + foreach (var modelStateEntry in context.ModelState.Values.Where(modelStateEntry => modelStateEntry.ValidationState == ModelValidationState.Unvalidated)) { modelStateEntry.ValidationState = ModelValidationState.Skipped; } } + else + { + logger.LogDebug("Skipping validation of unvalidated entries due to DisableBuiltInModelValidation being set to false."); + } } } } \ No newline at end of file diff --git a/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationObjectModelValidator.cs b/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationObjectModelValidator.cs index 6c5c6b6..31104f4 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationObjectModelValidator.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationObjectModelValidator.cs @@ -5,21 +5,9 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation { - public class FluentValidationAutoValidationObjectModelValidator : ObjectModelValidator + public class FluentValidationAutoValidationObjectModelValidator(IModelMetadataProvider modelMetadataProvider, IList validatorProviders, bool disableBuiltInModelValidation) : ObjectModelValidator(modelMetadataProvider, validatorProviders) { - private readonly bool disableBuiltInModelValidation; - - public FluentValidationAutoValidationObjectModelValidator(IModelMetadataProvider modelMetadataProvider, IList validatorProviders, bool disableBuiltInModelValidation) - : base(modelMetadataProvider, validatorProviders) - { - this.disableBuiltInModelValidation = disableBuiltInModelValidation; - } - - public override ValidationVisitor GetValidationVisitor(ActionContext actionContext, - IModelValidatorProvider validatorProvider, - ValidatorCache validatorCache, - IModelMetadataProvider metadataProvider, - ValidationStateDictionary? validationState) + public override ValidationVisitor GetValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary? validationState) { return new FluentValidationAutoValidationValidationVisitor(actionContext, validatorProvider, validatorCache, metadataProvider, validationState, disableBuiltInModelValidation); } diff --git a/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationValidationVisitor.cs b/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationValidationVisitor.cs index b72f240..22f54ff 100644 --- a/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationValidationVisitor.cs +++ b/FluentValidation.AutoValidation.Mvc/src/Validation/FluentValidationAutoValidationValidationVisitor.cs @@ -4,33 +4,12 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation { - public class FluentValidationAutoValidationValidationVisitor : ValidationVisitor + public class FluentValidationAutoValidationValidationVisitor(ActionContext actionContext, IModelValidatorProvider validatorProvider, ValidatorCache validatorCache, IModelMetadataProvider metadataProvider, ValidationStateDictionary? validationState, bool disableBuiltInModelValidation) : ValidationVisitor(actionContext, validatorProvider, validatorCache, metadataProvider, validationState) { - private readonly bool disableBuiltInModelValidation; - - public FluentValidationAutoValidationValidationVisitor(ActionContext actionContext, - IModelValidatorProvider validatorProvider, - ValidatorCache validatorCache, - IModelMetadataProvider metadataProvider, - ValidationStateDictionary? validationState, - bool disableBuiltInModelValidation) - : base(actionContext, validatorProvider, validatorCache, metadataProvider, validationState) - { - this.disableBuiltInModelValidation = disableBuiltInModelValidation; - } - - public override bool Validate(ModelMetadata? metadata, string? key, object? model, bool alwaysValidateAtTopLevel) - { - // If built in model validation is disabled return true for later validation in the action filter. - return disableBuiltInModelValidation || base.Validate(metadata, key, model, alwaysValidateAtTopLevel); - } - -#if !NETCOREAPP3_1 public override bool Validate(ModelMetadata? metadata, string? key, object? model, bool alwaysValidateAtTopLevel, object? container) { // If built in model validation is disabled return true for later validation in the action filter. return disableBuiltInModelValidation || base.Validate(metadata, key, model, alwaysValidateAtTopLevel, container); } -#endif } } \ No newline at end of file diff --git a/README.md b/README.md index 9de90bb..84be1da 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=SharpGrip_FluentValidation.AutoValidation&metric=coverage)](https://sonarcloud.io/summary/overall?id=SharpGrip_FluentValidation.AutoValidation) ## Upgrading + Check out the [upgrade guide](UPGRADING.md). ## Introduction @@ -75,17 +76,17 @@ app.MapPost("/", (SomeOtherModel someOtherModel) => $"Hello again {someOtherMode ### MVC controllers -| Property | Default value | Description | -|----------------------------------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 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 `[AutoValidationAttribute]` attribute. | -| EnableBodyBindingSourceAutomaticValidation | `true` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Body` binding sources (typically parameters decorated with the `[FromBody]` attribute). | -| EnableFormBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Form` binding sources (typically parameters decorated with the `[FromForm]` attribute). | -| EnableQueryBindingSourceAutomaticValidation | `true` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Query` binding sources (typically parameters decorated with the `[FromQuery]` attribute). | -| EnablePathBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Path` binding sources (typically parameters decorated with the `[FromRoute]` attribute). | -| EnableHeaderBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Header` binding sources (typically parameters decorated with the `[FromHeader]` attribute). | -| EnableCustomBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Custom` binding sources. | -| EnableNullBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters not bound from any binding source (typically parameters without a declared or inferred binding source). | +| Property | Default value | Description | +|----------------------------------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 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. Validation strategy `ValidationStrategy.Annotations` enables asynchronous automatic validation on controllers decorated (class or method) with a `[AutoValidationAttribute]` attribute. | +| EnableBodyBindingSourceAutomaticValidation | `true` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Body` binding sources (typically parameters decorated with the `[FromBody]` attribute). | +| EnableFormBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Form` binding sources (typically parameters decorated with the `[FromForm]` attribute). | +| EnableQueryBindingSourceAutomaticValidation | `true` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Query` binding sources (typically parameters decorated with the `[FromQuery]` attribute). | +| EnablePathBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Path` binding sources (typically parameters decorated with the `[FromRoute]` attribute). | +| EnableHeaderBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Header` binding sources (typically parameters decorated with the `[FromHeader]` attribute). | +| EnableCustomBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from `BindingSource.Custom` binding sources. | +| EnableNullBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters not bound from any binding source (typically parameters without a declared or inferred binding source). | ```cs using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions; diff --git a/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs b/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs index 44a3321..1836bab 100644 --- a/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs +++ b/Tests/src/FluentValidation.AutoValidation.Endpoints/Filters/FluentValidationAutoValidationEndpointFilterTest.cs @@ -9,6 +9,7 @@ using FluentValidation.Results; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.Extensions.Logging; using NSubstitute; using SharpGrip.FluentValidation.AutoValidation.Endpoints.Filters; using SharpGrip.FluentValidation.AutoValidation.Endpoints.Interceptors; @@ -28,6 +29,7 @@ public class FluentValidationAutoValidationEndpointFilterTest [Fact] public async Task TestInvokeAsync_ValidatorFound() { + var logger = Substitute.For>(); var serviceProvider = Substitute.For(); var endpointFilterInvocationContext = Substitute.For(); @@ -38,7 +40,7 @@ public async Task TestInvokeAsync_ValidatorFound() var validationFailuresValues = ValidationFailures.Values.ToList(); - var endpointFilter = new FluentValidationAutoValidationEndpointFilter(); + var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger); var result = (ValidationProblem) (await endpointFilter.InvokeAsync(endpointFilterInvocationContext, _ => ValueTask.FromResult(new object())!))!; var problemDetailsErrorValues = result.ProblemDetails.Errors.ToList(); @@ -51,6 +53,7 @@ public async Task TestInvokeAsync_ValidatorFound() [Fact] public async Task TestInvokeAsync_ValidatorNotFound() { + var logger = Substitute.For>(); var serviceProvider = Substitute.For(); var endpointFilterInvocationContext = Substitute.For(); @@ -59,7 +62,7 @@ public async Task TestInvokeAsync_ValidatorNotFound() serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(TestModel))).Returns(null); serviceProvider.GetService(typeof(IGlobalValidationInterceptor)).Returns(null); - var endpointFilter = new FluentValidationAutoValidationEndpointFilter(); + var endpointFilter = new FluentValidationAutoValidationEndpointFilter(logger); var result = await endpointFilter.InvokeAsync(endpointFilterInvocationContext, _ => ValueTask.FromResult(new object())!); diff --git a/Tests/src/FluentValidation.AutoValidation.Mvc/Filters/FluentValidationAutoValidationActionFilterTest.cs b/Tests/src/FluentValidation.AutoValidation.Mvc/Filters/FluentValidationAutoValidationActionFilterTest.cs index 18bbba0..93e85c3 100644 --- a/Tests/src/FluentValidation.AutoValidation.Mvc/Filters/FluentValidationAutoValidationActionFilterTest.cs +++ b/Tests/src/FluentValidation.AutoValidation.Mvc/Filters/FluentValidationAutoValidationActionFilterTest.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NSubstitute; using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration; @@ -66,6 +67,7 @@ public async Task TestOnActionExecutionAsync() var problemDetailsFactory = Substitute.For(); var fluentValidationAutoValidationResultFactory = Substitute.For(); var autoValidationMvcConfiguration = Substitute.For>(); + var logger = Substitute.For>(); var httpContext = Substitute.For(); var controller = Substitute.For(); var actionContext = Substitute.For(httpContext, Substitute.For(), controllerActionDescriptor, modelStateDictionary); @@ -84,7 +86,7 @@ public async Task TestOnActionExecutionAsync() actionExecutingContext.ActionArguments.Returns(actionArguments); autoValidationMvcConfiguration.Value.Returns(new AutoValidationMvcConfiguration()); - var actionFilter = new FluentValidationAutoValidationActionFilter(fluentValidationAutoValidationResultFactory, autoValidationMvcConfiguration); + var actionFilter = new FluentValidationAutoValidationActionFilter(fluentValidationAutoValidationResultFactory, autoValidationMvcConfiguration, logger); await actionFilter.OnActionExecutionAsync(actionExecutingContext, () => Task.FromResult(actionExecutedContext)); @@ -162,7 +164,9 @@ public async Task OnActionExecutionAsync_WithInstanceTypeDifferentThanParameterT var autoValidationMvcConfiguration = Substitute.For>(); autoValidationMvcConfiguration.Value.Returns(new AutoValidationMvcConfiguration()); - var actionFilter = new FluentValidationAutoValidationActionFilter(fluentValidationAutoValidationResultFactory, autoValidationMvcConfiguration); + var logger = Substitute.For>(); + + var actionFilter = new FluentValidationAutoValidationActionFilter(fluentValidationAutoValidationResultFactory, autoValidationMvcConfiguration, logger); // Act await actionFilter.OnActionExecutionAsync(actionExecutingContext, () => Task.FromResult(actionExecutedContext));