Skip to content

Added support for calling TryValidateModel #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<NoWarn>NU1701</NoWarn>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,11 @@ public static IServiceCollection AddFluentValidationAutoValidation(this IService
serviceCollection.Configure(autoValidationMvcConfiguration);
}

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

// Add the default result factory.
serviceCollection.AddScoped<IFluentValidationAutoValidationResultFactory, FluentValidationAutoValidationDefaultResultFactory>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Enums;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Interceptors;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Results;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Validation;
using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Filters
Expand Down Expand Up @@ -55,45 +55,16 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
if (actionExecutingContext.ActionArguments.TryGetValue(parameter.Name, out var subject))
{
var parameterInfo = (parameter as ControllerParameterDescriptor)?.ParameterInfo;
var parameterType = subject?.GetType();
var bindingSource = parameter.BindingInfo?.BindingSource;

var hasAutoValidateAlwaysAttribute = parameterInfo?.HasCustomAttribute<AutoValidateAlwaysAttribute>() ?? false;
var hasAutoValidateNeverAttribute = parameterInfo?.HasCustomAttribute<AutoValidateNeverAttribute>() ?? false;

if (subject != null && parameterType != null && parameterType.IsCustomType() &&
!hasAutoValidateNeverAttribute && (hasAutoValidateAlwaysAttribute || HasValidBindingSource(bindingSource)) &&
serviceProvider.GetValidator(parameterType) is IValidator validator)
if (!hasAutoValidateNeverAttribute && (hasAutoValidateAlwaysAttribute || HasValidBindingSource(bindingSource)))
{
// ReSharper disable once SuspiciousTypeConversion.Global
var validatorInterceptor = validator as IValidatorInterceptor;
var globalValidationInterceptor = serviceProvider.GetService<IGlobalValidationInterceptor>();

IValidationContext validationContext = new ValidationContext<object>(subject);

if (validatorInterceptor != null)
{
validationContext = validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
}

if (globalValidationInterceptor != null)
{
validationContext = globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
}

var validationResult = await validator.ValidateAsync(validationContext, actionExecutingContext.HttpContext.RequestAborted);

if (validatorInterceptor != null)
{
validationResult = validatorInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
}

if (globalValidationInterceptor != null)
{
validationResult = globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
}

if (!validationResult.IsValid)
var validationResult = await FluentValidationHelper.ValidateWithFluentValidationAsync(
serviceProvider, subject, actionExecutingContext);
if (validationResult != null && !validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,29 @@ public class FluentValidationAutoValidationObjectModelValidator : ObjectModelVal
{
private readonly bool disableBuiltInModelValidation;

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

public override ValidationVisitor GetValidationVisitor(ActionContext actionContext,
public override ValidationVisitor GetValidationVisitor(
ActionContext actionContext,
IModelValidatorProvider validatorProvider,
ValidatorCache validatorCache,
IModelMetadataProvider metadataProvider,
ValidationStateDictionary? validationState)
{
return new FluentValidationAutoValidationValidationVisitor(actionContext, validatorProvider, validatorCache, metadataProvider, validationState, disableBuiltInModelValidation);
return new FluentValidationAutoValidationValidationVisitor(
actionContext,
validatorProvider,
validatorCache,
metadataProvider,
validationState,
disableBuiltInModelValidation);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,82 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation
{
public class FluentValidationAutoValidationValidationVisitor : ValidationVisitor
{
private readonly ActionContext actionContext;
private readonly bool disableBuiltInModelValidation;

public FluentValidationAutoValidationValidationVisitor(ActionContext actionContext,
public FluentValidationAutoValidationValidationVisitor(
ActionContext actionContext,
IModelValidatorProvider validatorProvider,
ValidatorCache validatorCache,
IModelMetadataProvider metadataProvider,
ValidationStateDictionary? validationState,
bool disableBuiltInModelValidation)
: base(actionContext, validatorProvider, validatorCache, metadataProvider, validationState)
{
this.actionContext = actionContext;
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);
bool isBaseValid = disableBuiltInModelValidation || base.Validate(metadata, key, model, alwaysValidateAtTopLevel);
return ValidateAsync(isBaseValid, key, model).GetAwaiter().GetResult();
}

#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);
bool isBaseValid = disableBuiltInModelValidation || base.Validate(metadata, key, model, alwaysValidateAtTopLevel, container);
return ValidateAsync(isBaseValid, key, model).GetAwaiter().GetResult();
}
#endif

private async Task<bool> ValidateAsync(
bool defaultValue,
string? key,
object? model)
{
if (model == null)
{
return defaultValue;
}

var actionExecutingContext = new ActionExecutingContext(
actionContext,
new List<IFilterMetadata>(),
new Dictionary<string, object>(),
null);

var validationResult = await FluentValidationHelper.ValidateWithFluentValidationAsync(
actionContext.HttpContext.RequestServices,
model,
actionExecutingContext);
if (validationResult == null)
{
return defaultValue;
}

foreach (var error in validationResult.Errors)
{
var keyName = string.IsNullOrEmpty(key) ? error.PropertyName : $"{key}.{error.PropertyName}";
if (!this.ModelState[keyName]?.Errors.Any(e => e.ErrorMessage == error.ErrorMessage) ?? true)
{
this.ModelState.AddModelError(keyName, error.ErrorMessage);
}
}

return defaultValue && validationResult.IsValid;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Interceptors;
using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Validation
{
public static class FluentValidationHelper
{
public static async Task<ValidationResult?> ValidateWithFluentValidationAsync(
IServiceProvider serviceProvider,
object? model,
ActionExecutingContext actionExecutingContext)
{
if (model == null)
{
return null;
}

var modelType = model.GetType();
if (modelType == null)
{
return null;
}

if (!modelType.IsCustomType())
{
return null;
}

var validator = serviceProvider.GetValidator(modelType) as IValidator;
if (validator == null)
{
return null;
}

IValidationContext validationContext = new ValidationContext<object>(model);

var validatorInterceptor = validator as IValidatorInterceptor;
if (validatorInterceptor != null)
{
validationContext = validatorInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
}

var globalValidationInterceptor = serviceProvider.GetService<IGlobalValidationInterceptor>();
if (globalValidationInterceptor != null)
{
validationContext = globalValidationInterceptor.BeforeValidation(actionExecutingContext, validationContext) ?? validationContext;
}

var validationResult = await validator.ValidateAsync(validationContext, actionExecutingContext.HttpContext.RequestAborted);

if (validatorInterceptor != null)
{
validationResult = validatorInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
}

if (globalValidationInterceptor != null)
{
validationResult = globalValidationInterceptor.AfterValidation(actionExecutingContext, validationContext) ?? validationResult;
}

return validationResult;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void TestAddFluentValidationAutoValidation()

AssertNotContainsServiceDescriptor<IFluentValidationAutoValidationResultFactory, TestResultFactory>(serviceCollection, ServiceLifetime.Scoped);
AssertContainsServiceDescriptor<IFluentValidationAutoValidationResultFactory, FluentValidationAutoValidationDefaultResultFactory>(serviceCollection, ServiceLifetime.Scoped);
AssertNotContainsServiceDescriptor<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceCollection, ServiceLifetime.Singleton);
AssertContainsServiceDescriptor<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceCollection, ServiceLifetime.Singleton);
AssertContainsServiceDescriptor<IConfigureOptions<MvcOptions>, ConfigureNamedOptions<MvcOptions>>(serviceCollection, ServiceLifetime.Singleton);
}

Expand All @@ -34,7 +34,7 @@ public void TestAddFluentValidationAutoValidation_WithConfiguration_OverriddenRe

AssertContainsServiceDescriptor<IFluentValidationAutoValidationResultFactory, TestResultFactory>(serviceCollection, ServiceLifetime.Scoped);
AssertNotContainsServiceDescriptor<IFluentValidationAutoValidationResultFactory, FluentValidationAutoValidationDefaultResultFactory>(serviceCollection, ServiceLifetime.Scoped);
AssertNotContainsServiceDescriptor<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceCollection, ServiceLifetime.Singleton);
AssertContainsServiceDescriptor<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceCollection, ServiceLifetime.Singleton);
AssertContainsServiceDescriptor<IConfigureOptions<MvcOptions>, ConfigureNamedOptions<MvcOptions>>(serviceCollection, ServiceLifetime.Singleton);
}

Expand All @@ -47,7 +47,7 @@ public void TestAddFluentValidationAutoValidation_WithConfiguration_DisableBuilt

AssertNotContainsServiceDescriptor<IFluentValidationAutoValidationResultFactory, TestResultFactory>(serviceCollection, ServiceLifetime.Scoped);
AssertContainsServiceDescriptor<IFluentValidationAutoValidationResultFactory, FluentValidationAutoValidationDefaultResultFactory>(serviceCollection, ServiceLifetime.Scoped);
AssertNotContainsServiceDescriptor<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceCollection, ServiceLifetime.Singleton);
AssertContainsServiceDescriptor<IObjectModelValidator, FluentValidationAutoValidationObjectModelValidator>(serviceCollection, ServiceLifetime.Singleton);
AssertContainsServiceDescriptor<IConfigureOptions<MvcOptions>, ConfigureNamedOptions<MvcOptions>>(serviceCollection, ServiceLifetime.Singleton);
}

Expand Down
Loading