Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public FluentValidationAutoValidationEndpointFilter(IServiceProvider serviceProv
{
var argument = context.Arguments[i];

if (argument != null && serviceProvider.GetValidator(argument.GetType()) is IValidator validator)
if (argument != null && argument.GetType().IsCustomType() && serviceProvider.GetValidator(argument.GetType()) is IValidator validator)
{
var validationResult = await validator.ValidateAsync(new ValidationContext<object>(argument), context.HttpContext.RequestAborted);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Results;
Expand All @@ -19,6 +20,21 @@ public class AutoValidationMvcConfiguration
/// </summary>
public ValidationStrategy ValidationStrategy { get; set; } = ValidationStrategy.All;

/// <summary>
/// Enables asynchronous automatic validation for parameters bound from the <see cref="BindingSource.Body"/> binding source (typically parameters decorated with the [FormBody] attribute).
/// </summary>
public bool EnableBodyBindingSourceAutomaticValidation { get; set; } = true;

/// <summary>
/// Enables asynchronous automatic validation for parameters bound from the <see cref="BindingSource.Form"/> binding source (typically parameters decorated with the [FromForm] attribute).
/// </summary>
public bool EnableFormBindingSourceAutomaticValidation { get; set; } = false;

/// <summary>
/// Enables asynchronous automatic validation for parameters bound from the <see cref="BindingSource.Query"/> binding source (typically parameters decorated with the [FormQuery] attribute).
/// </summary>
public bool EnableQueryBindingSourceAutomaticValidation { get; set; } = true;

/// <summary>
/// Holds the overridden result factory. This property is meant for infrastructure and should not be used by application code.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
var parameterType = parameter.ParameterType;
var bindingSource = parameter.BindingInfo?.BindingSource;

if (subject != null && (bindingSource == BindingSource.Body || (bindingSource == BindingSource.Query && parameterType.IsClass)))
if (subject != null && parameterType.IsCustomType() &&
((autoValidationMvcConfiguration.EnableBodyBindingSourceAutomaticValidation && bindingSource == BindingSource.Body) ||
(autoValidationMvcConfiguration.EnableFormBindingSourceAutomaticValidation && bindingSource == BindingSource.Form) ||
(autoValidationMvcConfiguration.EnableQueryBindingSourceAutomaticValidation && bindingSource == BindingSource.Query)))
{
if (serviceProvider.GetValidator(parameterType) is IValidator validator)
{
Expand All @@ -70,6 +73,14 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
}
}

if (autoValidationMvcConfiguration.DisableBuiltInModelValidation)
{
foreach (var modelStateEntry in context.ModelState.Values.Where(modelStateEntry => modelStateEntry.ValidationState == ModelValidationState.Unvalidated))
{
modelStateEntry.ValidationState = ModelValidationState.Skipped;
}
}

if (!context.ModelState.IsValid)
{
var validationProblemDetails = controllerBase.ProblemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

Expand All @@ -12,8 +8,6 @@ public class FluentValidationAutoValidationValidationVisitor : ValidationVisitor
{
private readonly bool disableBuiltInModelValidation;

private readonly List<Type> systemTypes = Assembly.GetExecutingAssembly().GetType().Module.Assembly.GetExportedTypes().ToList();

public FluentValidationAutoValidationValidationVisitor(ActionContext actionContext,
IModelValidatorProvider validatorProvider,
ValidatorCache validatorCache,
Expand All @@ -27,33 +21,16 @@ public FluentValidationAutoValidationValidationVisitor(ActionContext actionConte

public override bool Validate(ModelMetadata? metadata, string? key, object? model, bool alwaysValidateAtTopLevel)
{
// For non-system class types return true for later validation in the action filter. For all other (system) types return the base validation result.
if (IsBuiltInValidationDisabledAndTypeIsClassAndNotSystemType(model))
{
return true;
}

return base.Validate(metadata, key, model, 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)
{
// For non-system class types return true for later validation in the action filter. For all other (system) types return the base validation result.
if (IsBuiltInValidationDisabledAndTypeIsClassAndNotSystemType(model))
{
return true;
}

return base.Validate(metadata, key, model, alwaysValidateAtTopLevel, 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

private bool IsBuiltInValidationDisabledAndTypeIsClassAndNotSystemType(object? model)
{
var modelType = model?.GetType();

return disableBuiltInModelValidation && modelType != null && modelType.IsClass && !systemTypes.Contains(modelType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Linq;

namespace SharpGrip.FluentValidation.AutoValidation.Shared.Extensions
{
public static class TypeExtensions
{
public static bool IsCustomType(this Type? type)
{
var builtInTypes = new[]
{
typeof(string),
typeof(decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(Guid)
};

return type != null && type.IsClass && !type.IsEnum && !type.IsValueType && !type.IsPrimitive && !builtInTypes.Contains(type);
}
}
}
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@ 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 `FluentValidationAutoValidationAttribute` attribute. |
| 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 `FluentValidationAutoValidationAttribute` attribute. |
| EnableBodyBindingSourceAutomaticValidation | `true` | Enables asynchronous automatic validation for parameters bound from the `BindingSource.Body` binding source (typically parameters decorated with the `[FormBody]` attribute). |
| EnableFormBindingSourceAutomaticValidation | `false` | Enables asynchronous automatic validation for parameters bound from the `BindingSource.Form` binding source (typically parameters decorated with the `[FormForm]` attribute). |
| EnableQueryBindingSourceAutomaticValidation | `true` | Enables asynchronous automatic validation for parameters bound from the `BindingSource.Query` binding source (typically parameters decorated with the `[FormQuery]` attribute). |

```
using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
Expand All @@ -68,6 +71,15 @@ builder.Services.AddFluentValidationAutoValidation(configuration =>
// Only validate controllers decorated with the `FluentValidationAutoValidation` attribute.
configuration.ValidationStrategy = ValidationStrategy.Annotation;

// Enable validation for parameters bound from the `BindingSource.Body` binding source.
configuration.EnableBodyBindingSourceAutomaticValidation = true;

// Enable validation for parameters bound from the `BindingSource.Form` binding source.
configuration.EnableFormBindingSourceAutomaticValidation = true;

// Enable validation for parameters bound from the `BindingSource.Query` binding source.
configuration.EnableQueryBindingSourceAutomaticValidation = true;

// Replace the default result factory with a custom implementation.
configuration.OverrideDefaultResultFactoryWith<CustomResultFactory>();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using FluentValidation.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Extensions;
using SharpGrip.FluentValidation.AutoValidation.Endpoints.Results;
using Xunit;
Expand Down Expand Up @@ -42,6 +40,7 @@ private static void AssertContainsServiceDescriptor<TService, TImplementation>(S
serviceDescriptor.ImplementationInstance?.GetType() == typeof(TImplementation)) &&
serviceDescriptor.Lifetime == serviceLifetime);
}
// ReSharper restore ParameterOnlyUsedForPreconditionCheck.Local

// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local
private static void AssertNotContainsServiceDescriptor<TService, TImplementation>(ServiceCollection serviceCollection, ServiceLifetime serviceLifetime)
Expand All @@ -52,6 +51,7 @@ private static void AssertNotContainsServiceDescriptor<TService, TImplementation
serviceDescriptor.ImplementationInstance?.GetType() == typeof(TImplementation)) &&
serviceDescriptor.Lifetime == serviceLifetime);
}
// ReSharper restore ParameterOnlyUsedForPreconditionCheck.Local

private class TestResultFactory : IFluentValidationAutoValidationResultFactory
{
Expand Down