-
Notifications
You must be signed in to change notification settings - Fork 17
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @icnocop this method feels kinda hacky by taking a IServiceProvider as an argument, what do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What else do you recommend?
|
||
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; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@icnocop not sure about this one, won't this result in deadlocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In ASP.NET Core, it should not cause a deadlock, according to ChatGPT 4.1.
In which scenario(s) are you thinking it could cause a deadlock?
Maybe a test can be written to verify it.