Skip to content

Commit

Permalink
fix disabling built-in model validations
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdgun committed Aug 18, 2023
1 parent a74722b commit 7e4fc09
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<AssemblyName>SharpGrip.FluentValidation.AutoValidation.Mvc</AssemblyName>
<PackageId>SharpGrip.FluentValidation.AutoValidation.Mvc</PackageId>
<Title>SharpGrip FluentValidation AutoValidation MVC</Title>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration
{
public class AutoValidationMvcConfiguration
{
public bool DisableDataAnnotationsValidation { get; set; }
/// <summary>
/// Disables the built-in model validation.
/// </summary>
public bool DisableBuiltInModelValidation { get; set; }

/// <summary>
/// Configures the validation strategy. Validation strategy <see cref="T:SharpGrip.FluentValidation.AutoValidation.Mvc.Enums.ValidationStrategy"/> enables asynchronous automatic validation on all controllers inheriting from <see cref="T:Microsoft.AspNetCore.Mvc.ControllerBase"/>.
/// Validation strategy <see cref="SharpGrip.FluentValidation.AutoValidation.Mvc.Enums.ValidationStrategy.Annotations"/> enables asynchronous automatic validation on controllers inheriting from <see cref="T:Microsoft.AspNetCore.Mvc.ControllerBase"/> decorated (class or method) with a <see cref="T:SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes.FluentValidationAutoValidationAttribute"/> attribute.
/// </summary>
public ValidationStrategy ValidationStrategy { get; set; } = ValidationStrategy.All;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
public enum ValidationStrategy
{
/// <summary>
/// Enables asynchronous automatic validation on all controllers inheriting from `Microsoft.AspNetCore.Mvc.ControllerBase`.
/// Enables asynchronous automatic validation on all controllers inheriting from <see cref="T:Microsoft.AspNetCore.Mvc.ControllerBase"/>.
/// </summary>
All = 1,

/// <summary>
/// Enables asynchronous automatic validation on controllers inheriting from `Microsoft.AspNetCore.Mvc.ControllerBase` decorated with a `SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes.FluentValidationAutoValidationAttribute` attribute.
/// Enables asynchronous automatic validation on controllers inheriting from <see cref="T:Microsoft.AspNetCore.Mvc.ControllerBase"/> decorated with a <see cref="T:SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes.FluentValidationAutoValidationAttribute"/> attribute.
/// </summary>
Annotation = 2

//Validation strategy `ValidationStrategy.All` enables automatic validation on all instances of `ControllerBase`. Validation strategy `ValidationStrategy.Annotations` only enables automatic validation on all instances of `ControllerBase` with class or method decorations of the `FluentValidationAutoValidation` attribute.
Annotations = 2
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection;
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.Validation;
Expand All @@ -20,17 +22,21 @@ public static class ServiceCollectionExtensions
/// <returns>The service collection.</returns>
public static IServiceCollection AddFluentValidationAutoValidation(this IServiceCollection serviceCollection, Action<AutoValidationMvcConfiguration>? autoValidationMvcConfiguration = null)
{
var defaultAutoValidationMvcConfiguration = new AutoValidationMvcConfiguration();
var configuration = new AutoValidationMvcConfiguration();

if (autoValidationMvcConfiguration != null)
{
autoValidationMvcConfiguration.Invoke(defaultAutoValidationMvcConfiguration);
autoValidationMvcConfiguration.Invoke(configuration);
serviceCollection.Configure(autoValidationMvcConfiguration);
}

if (defaultAutoValidationMvcConfiguration.DisableDataAnnotationsValidation)
if (configuration.DisableBuiltInModelValidation)
{
serviceCollection.AddSingleton<IObjectModelValidator, NullObjectModelValidator>();
serviceCollection.AddSingleton<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceProvider =>
new FluentValidationAutoValidationObjectModelValidator(
serviceProvider.GetRequiredService<IModelMetadataProvider>(),
serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value.ModelValidatorProviders,
configuration.DisableBuiltInModelValidation));
}

// Create a default instance of the `ModelStateInvalidFilter` to access the non static property `Order` in a static context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
var actionDescriptor = context.ActionDescriptor;

// @todo figure out a better way to retrieve the attribute since using the `context.ActionDescriptor.EndpointMetadata` is not recommended for application code
if (autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotation && !actionDescriptor.EndpointMetadata.OfType<FluentValidationAutoValidationAttribute>().Any())
if (autoValidationMvcConfiguration.ValidationStrategy == ValidationStrategy.Annotations && !actionDescriptor.EndpointMetadata.OfType<FluentValidationAutoValidationAttribute>().Any())
{
await next();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation
{
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,
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)
{
// 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 !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);
}
#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,27 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation
{
public class FluentValidationAutoValidationObjectModelValidator : ObjectModelValidator
{
private readonly bool disableBuiltInModelValidation;

public FluentValidationAutoValidationObjectModelValidator(IModelMetadataProvider modelMetadataProvider, IList<IModelValidatorProvider> validatorProviders, bool disableBuiltInModelValidation)
: base(modelMetadataProvider, validatorProviders)
{
this.disableBuiltInModelValidation = disableBuiltInModelValidation;
}

public override ValidationVisitor GetValidationVisitor(ActionContext actionContext,
IModelValidatorProvider validatorProvider,
ValidatorCache validatorCache,
IModelMetadataProvider metadataProvider,
ValidationStateDictionary? validationState)
{
return new FluentValidationAutoValidationValidationVisitor(actionContext, validatorProvider, validatorCache, metadataProvider, validationState, disableBuiltInModelValidation);
}
}
}

This file was deleted.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ app.MapPost("/", (SomeOtherModel someOtherModel) => $"Hello again {someOtherMode

### MVC controllers

| Property | Default value | Description |
|----------------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DisableDataAnnotationsValidation | `false` | Disables the built-in model 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 model 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. |

```
using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
Expand Down

0 comments on commit 7e4fc09

Please sign in to comment.