Open up private methods in ValidationVisitor
- Allows for other validation libraries to customize validation behaviour Addresses #7014
This commit is contained in:
parent
959e7027b0
commit
3a132e4af5
|
|
@ -15,19 +15,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ValidationVisitor
|
public class ValidationVisitor
|
||||||
{
|
{
|
||||||
private readonly IModelValidatorProvider _validatorProvider;
|
protected IModelValidatorProvider ValidatorProvider { get; }
|
||||||
private readonly IModelMetadataProvider _metadataProvider;
|
protected IModelMetadataProvider MetadataProvider { get; }
|
||||||
private readonly ValidatorCache _validatorCache;
|
protected ValidatorCache Cache { get; }
|
||||||
private readonly ActionContext _actionContext;
|
protected ActionContext Context { get; }
|
||||||
private readonly ModelStateDictionary _modelState;
|
protected ModelStateDictionary ModelState { get; }
|
||||||
private readonly ValidationStateDictionary _validationState;
|
protected ValidationStateDictionary ValidationState { get; }
|
||||||
private readonly ValidationStack _currentPath;
|
protected ValidationStack CurrentPath { get; }
|
||||||
|
|
||||||
private object _container;
|
protected object Container { get; set; }
|
||||||
private string _key;
|
protected string Key { get; set; }
|
||||||
private object _model;
|
protected object Model { get; set; }
|
||||||
private ModelMetadata _metadata;
|
protected ModelMetadata Metadata { get; set; }
|
||||||
private IValidationStrategy _strategy;
|
protected IValidationStrategy Strategy { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false.
|
/// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false.
|
||||||
|
|
@ -64,15 +64,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
throw new ArgumentNullException(nameof(validatorCache));
|
throw new ArgumentNullException(nameof(validatorCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
_actionContext = actionContext;
|
Context = actionContext;
|
||||||
_validatorProvider = validatorProvider;
|
ValidatorProvider = validatorProvider;
|
||||||
_validatorCache = validatorCache;
|
Cache = validatorCache;
|
||||||
|
|
||||||
_metadataProvider = metadataProvider;
|
MetadataProvider = metadataProvider;
|
||||||
_validationState = validationState;
|
ValidationState = validationState;
|
||||||
|
|
||||||
_modelState = actionContext.ModelState;
|
ModelState = actionContext.ModelState;
|
||||||
_currentPath = new ValidationStack();
|
CurrentPath = new ValidationStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
/// <param name="key">The model prefix key.</param>
|
/// <param name="key">The model prefix key.</param>
|
||||||
/// <param name="model">The model object.</param>
|
/// <param name="model">The model object.</param>
|
||||||
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
|
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
|
||||||
public bool Validate(ModelMetadata metadata, string key, object model)
|
public virtual bool Validate(ModelMetadata metadata, string key, object model)
|
||||||
{
|
{
|
||||||
return Validate(metadata, key, model, alwaysValidateAtTopLevel: false);
|
return Validate(metadata, key, model, alwaysValidateAtTopLevel: false);
|
||||||
}
|
}
|
||||||
|
|
@ -95,11 +95,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
/// <param name="model">The model object.</param>
|
/// <param name="model">The model object.</param>
|
||||||
/// <param name="alwaysValidateAtTopLevel">If <c>true</c>, applies validation rules even if the top-level value is <c>null</c>.</param>
|
/// <param name="alwaysValidateAtTopLevel">If <c>true</c>, applies validation rules even if the top-level value is <c>null</c>.</param>
|
||||||
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
|
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
|
||||||
public bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel)
|
public virtual bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel)
|
||||||
{
|
{
|
||||||
if (model == null && key != null && !alwaysValidateAtTopLevel)
|
if (model == null && key != null && !alwaysValidateAtTopLevel)
|
||||||
{
|
{
|
||||||
var entry = _modelState[key];
|
var entry = ModelState[key];
|
||||||
if (entry != null && entry.ValidationState != ModelValidationState.Valid)
|
if (entry != null && entry.ValidationState != ModelValidationState.Valid)
|
||||||
{
|
{
|
||||||
entry.ValidationState = ModelValidationState.Valid;
|
entry.ValidationState = ModelValidationState.Valid;
|
||||||
|
|
@ -117,23 +117,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
/// <returns><c>true</c> if the node is valid, otherwise <c>false</c>.</returns>
|
/// <returns><c>true</c> if the node is valid, otherwise <c>false</c>.</returns>
|
||||||
protected virtual bool ValidateNode()
|
protected virtual bool ValidateNode()
|
||||||
{
|
{
|
||||||
var state = _modelState.GetValidationState(_key);
|
var state = ModelState.GetValidationState(Key);
|
||||||
|
|
||||||
// Rationale: we might see the same model state key used for two different objects.
|
// Rationale: we might see the same model state key used for two different objects.
|
||||||
// We want to run validation unless it's already known that this key is invalid.
|
// We want to run validation unless it's already known that this key is invalid.
|
||||||
if (state != ModelValidationState.Invalid)
|
if (state != ModelValidationState.Invalid)
|
||||||
{
|
{
|
||||||
var validators = _validatorCache.GetValidators(_metadata, _validatorProvider);
|
var validators = Cache.GetValidators(Metadata, ValidatorProvider);
|
||||||
|
|
||||||
var count = validators.Count;
|
var count = validators.Count;
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
var context = new ModelValidationContext(
|
var context = new ModelValidationContext(
|
||||||
_actionContext,
|
Context,
|
||||||
_metadata,
|
Metadata,
|
||||||
_metadataProvider,
|
MetadataProvider,
|
||||||
_container,
|
Container,
|
||||||
_model);
|
Model);
|
||||||
|
|
||||||
var results = new List<ModelValidationResult>();
|
var results = new List<ModelValidationResult>();
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
|
|
@ -145,21 +145,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
for (var i = 0; i < resultsCount; i++)
|
for (var i = 0; i < resultsCount; i++)
|
||||||
{
|
{
|
||||||
var result = results[i];
|
var result = results[i];
|
||||||
var key = ModelNames.CreatePropertyModelName(_key, result.MemberName);
|
var key = ModelNames.CreatePropertyModelName(Key, result.MemberName);
|
||||||
|
|
||||||
// If this is a top-level parameter/property, the key would be empty,
|
// If this is a top-level parameter/property, the key would be empty,
|
||||||
// so use the name of the top-level property
|
// so use the name of the top-level property
|
||||||
if (string.IsNullOrEmpty(key) && _metadata.PropertyName != null)
|
if (string.IsNullOrEmpty(key) && Metadata.PropertyName != null)
|
||||||
{
|
{
|
||||||
key = _metadata.PropertyName;
|
key = Metadata.PropertyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
_modelState.TryAddModelError(key, result.Message);
|
ModelState.TryAddModelError(key, result.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state = _modelState.GetFieldValidationState(_key);
|
state = ModelState.GetFieldValidationState(Key);
|
||||||
if (state == ModelValidationState.Invalid)
|
if (state == ModelValidationState.Invalid)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -168,7 +168,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
{
|
{
|
||||||
// If the field has an entry in ModelState, then record it as valid. Don't create
|
// If the field has an entry in ModelState, then record it as valid. Don't create
|
||||||
// extra entries if they don't exist already.
|
// extra entries if they don't exist already.
|
||||||
var entry = _modelState[_key];
|
var entry = ModelState[Key];
|
||||||
if (entry != null)
|
if (entry != null)
|
||||||
{
|
{
|
||||||
entry.ValidationState = ModelValidationState.Valid;
|
entry.ValidationState = ModelValidationState.Valid;
|
||||||
|
|
@ -178,11 +178,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Visit(ModelMetadata metadata, string key, object model)
|
protected virtual bool Visit(ModelMetadata metadata, string key, object model)
|
||||||
{
|
{
|
||||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||||
|
|
||||||
if (model != null && !_currentPath.Push(model))
|
if (model != null && !CurrentPath.Push(model))
|
||||||
{
|
{
|
||||||
// This is a cycle, bail.
|
// This is a cycle, bail.
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
metadata = entry?.Metadata ?? metadata;
|
metadata = entry?.Metadata ?? metadata;
|
||||||
var strategy = entry?.Strategy;
|
var strategy = entry?.Strategy;
|
||||||
|
|
||||||
if (_modelState.HasReachedMaxErrors)
|
if (ModelState.HasReachedMaxErrors)
|
||||||
{
|
{
|
||||||
SuppressValidation(key);
|
SuppressValidation(key);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -202,18 +202,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
{
|
{
|
||||||
// Use the key on the entry, because we might not have entries in model state.
|
// Use the key on the entry, because we might not have entries in model state.
|
||||||
SuppressValidation(entry.Key);
|
SuppressValidation(entry.Key);
|
||||||
_currentPath.Pop(model);
|
CurrentPath.Pop(model);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy))
|
using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy))
|
||||||
{
|
{
|
||||||
if (_metadata.IsEnumerableType)
|
if (Metadata.IsEnumerableType)
|
||||||
{
|
{
|
||||||
return VisitComplexType(DefaultCollectionValidationStrategy.Instance);
|
return VisitComplexType(DefaultCollectionValidationStrategy.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_metadata.IsComplexType)
|
if (Metadata.IsComplexType)
|
||||||
{
|
{
|
||||||
return VisitComplexType(DefaultComplexObjectValidationStrategy.Instance);
|
return VisitComplexType(DefaultComplexObjectValidationStrategy.Instance);
|
||||||
}
|
}
|
||||||
|
|
@ -223,26 +223,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Covers everything VisitSimpleType does not i.e. both enumerations and complex types.
|
// Covers everything VisitSimpleType does not i.e. both enumerations and complex types.
|
||||||
private bool VisitComplexType(IValidationStrategy defaultStrategy)
|
protected virtual bool VisitComplexType(IValidationStrategy defaultStrategy)
|
||||||
{
|
{
|
||||||
var isValid = true;
|
var isValid = true;
|
||||||
|
|
||||||
if (_model != null && _metadata.ValidateChildren)
|
if (Model != null && Metadata.ValidateChildren)
|
||||||
{
|
{
|
||||||
var strategy = _strategy ?? defaultStrategy;
|
var strategy = Strategy ?? defaultStrategy;
|
||||||
isValid = VisitChildren(strategy);
|
isValid = VisitChildren(strategy);
|
||||||
}
|
}
|
||||||
else if (_model != null)
|
else if (Model != null)
|
||||||
{
|
{
|
||||||
// Suppress validation for the entries matching this prefix. This will temporarily set
|
// Suppress validation for the entries matching this prefix. This will temporarily set
|
||||||
// the current node to 'skipped' but we're going to visit it right away, so subsequent
|
// the current node to 'skipped' but we're going to visit it right away, so subsequent
|
||||||
// code will set it to 'valid' or 'invalid'
|
// code will set it to 'valid' or 'invalid'
|
||||||
SuppressValidation(_key);
|
SuppressValidation(Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-checking HasReachedMaxErrors just in case this model has no properties.
|
// Double-checking HasReachedMaxErrors just in case this model has no properties.
|
||||||
// If validation has failed for any children, only validate the parent if ValidateComplexTypesIfChildValidationFails is true.
|
// If validation has failed for any children, only validate the parent if ValidateComplexTypesIfChildValidationFails is true.
|
||||||
if ((isValid || ValidateComplexTypesIfChildValidationFails) && !_modelState.HasReachedMaxErrors)
|
if ((isValid || ValidateComplexTypesIfChildValidationFails) && !ModelState.HasReachedMaxErrors)
|
||||||
{
|
{
|
||||||
isValid &= ValidateNode();
|
isValid &= ValidateNode();
|
||||||
}
|
}
|
||||||
|
|
@ -250,22 +250,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool VisitSimpleType()
|
protected virtual bool VisitSimpleType()
|
||||||
{
|
{
|
||||||
if (_modelState.HasReachedMaxErrors)
|
if (ModelState.HasReachedMaxErrors)
|
||||||
{
|
{
|
||||||
SuppressValidation(_key);
|
SuppressValidation(Key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValidateNode();
|
return ValidateNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool VisitChildren(IValidationStrategy strategy)
|
protected virtual bool VisitChildren(IValidationStrategy strategy)
|
||||||
{
|
{
|
||||||
var isValid = true;
|
var isValid = true;
|
||||||
var enumerator = strategy.GetChildren(_metadata, _key, _model);
|
var enumerator = strategy.GetChildren(Metadata, Key, Model);
|
||||||
var parentEntry = new ValidationEntry(_metadata, _key, _model);
|
var parentEntry = new ValidationEntry(Metadata, Key, Model);
|
||||||
|
|
||||||
while (enumerator.MoveNext())
|
while (enumerator.MoveNext())
|
||||||
{
|
{
|
||||||
|
|
@ -284,7 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SuppressValidation(string key)
|
protected virtual void SuppressValidation(string key)
|
||||||
{
|
{
|
||||||
if (key == null)
|
if (key == null)
|
||||||
{
|
{
|
||||||
|
|
@ -293,26 +293,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = _modelState.FindKeysWithPrefix(key);
|
var entries = ModelState.FindKeysWithPrefix(key);
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
entry.Value.ValidationState = ModelValidationState.Skipped;
|
entry.Value.ValidationState = ModelValidationState.Skipped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationStateEntry GetValidationEntry(object model)
|
protected virtual ValidationStateEntry GetValidationEntry(object model)
|
||||||
{
|
{
|
||||||
if (model == null || _validationState == null)
|
if (model == null || ValidationState == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidationStateEntry entry;
|
ValidationStateEntry entry;
|
||||||
_validationState.TryGetValue(model, out entry);
|
ValidationState.TryGetValue(model, out entry);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct StateManager : IDisposable
|
protected struct StateManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ValidationVisitor _visitor;
|
private readonly ValidationVisitor _visitor;
|
||||||
private readonly object _container;
|
private readonly object _container;
|
||||||
|
|
@ -331,11 +331,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
{
|
{
|
||||||
var recursifier = new StateManager(visitor, model);
|
var recursifier = new StateManager(visitor, model);
|
||||||
|
|
||||||
visitor._container = visitor._model;
|
visitor.Container = visitor.Model;
|
||||||
visitor._key = key;
|
visitor.Key = key;
|
||||||
visitor._metadata = metadata;
|
visitor.Metadata = metadata;
|
||||||
visitor._model = model;
|
visitor.Model = model;
|
||||||
visitor._strategy = strategy;
|
visitor.Strategy = strategy;
|
||||||
|
|
||||||
return recursifier;
|
return recursifier;
|
||||||
}
|
}
|
||||||
|
|
@ -345,22 +345,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
|
||||||
_visitor = visitor;
|
_visitor = visitor;
|
||||||
_newModel = newModel;
|
_newModel = newModel;
|
||||||
|
|
||||||
_container = _visitor._container;
|
_container = _visitor.Container;
|
||||||
_key = _visitor._key;
|
_key = _visitor.Key;
|
||||||
_metadata = _visitor._metadata;
|
_metadata = _visitor.Metadata;
|
||||||
_model = _visitor._model;
|
_model = _visitor.Model;
|
||||||
_strategy = _visitor._strategy;
|
_strategy = _visitor.Strategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_visitor._container = _container;
|
_visitor.Container = _container;
|
||||||
_visitor._key = _key;
|
_visitor.Key = _key;
|
||||||
_visitor._metadata = _metadata;
|
_visitor.Metadata = _metadata;
|
||||||
_visitor._model = _model;
|
_visitor.Model = _model;
|
||||||
_visitor._strategy = _strategy;
|
_visitor.Strategy = _strategy;
|
||||||
|
|
||||||
_visitor._currentPath.Pop(_newModel);
|
_visitor.CurrentPath.Pop(_newModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue