Open up private methods in ValidationVisitor

- Allows for other validation libraries to customize validation behaviour

Addresses #7014
This commit is contained in:
Jeremy Skinner 2017-11-21 19:49:43 +00:00 committed by Ryan Nowak
parent 959e7027b0
commit 3a132e4af5
1 changed files with 75 additions and 75 deletions

View File

@ -15,19 +15,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
/// </summary>
public class ValidationVisitor
{
private readonly IModelValidatorProvider _validatorProvider;
private readonly IModelMetadataProvider _metadataProvider;
private readonly ValidatorCache _validatorCache;
private readonly ActionContext _actionContext;
private readonly ModelStateDictionary _modelState;
private readonly ValidationStateDictionary _validationState;
private readonly ValidationStack _currentPath;
protected IModelValidatorProvider ValidatorProvider { get; }
protected IModelMetadataProvider MetadataProvider { get; }
protected ValidatorCache Cache { get; }
protected ActionContext Context { get; }
protected ModelStateDictionary ModelState { get; }
protected ValidationStateDictionary ValidationState { get; }
protected ValidationStack CurrentPath { get; }
private object _container;
private string _key;
private object _model;
private ModelMetadata _metadata;
private IValidationStrategy _strategy;
protected object Container { get; set; }
protected string Key { get; set; }
protected object Model { get; set; }
protected ModelMetadata Metadata { get; set; }
protected IValidationStrategy Strategy { get; set; }
/// <summary>
/// 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));
}
_actionContext = actionContext;
_validatorProvider = validatorProvider;
_validatorCache = validatorCache;
Context = actionContext;
ValidatorProvider = validatorProvider;
Cache = validatorCache;
_metadataProvider = metadataProvider;
_validationState = validationState;
MetadataProvider = metadataProvider;
ValidationState = validationState;
_modelState = actionContext.ModelState;
_currentPath = new ValidationStack();
ModelState = actionContext.ModelState;
CurrentPath = new ValidationStack();
}
/// <summary>
@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
/// <param name="key">The model prefix key.</param>
/// <param name="model">The model object.</param>
/// <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);
}
@ -95,11 +95,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
/// <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>
/// <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)
{
var entry = _modelState[key];
var entry = ModelState[key];
if (entry != null && 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>
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.
// We want to run validation unless it's already known that this key is invalid.
if (state != ModelValidationState.Invalid)
{
var validators = _validatorCache.GetValidators(_metadata, _validatorProvider);
var validators = Cache.GetValidators(Metadata, ValidatorProvider);
var count = validators.Count;
if (count > 0)
{
var context = new ModelValidationContext(
_actionContext,
_metadata,
_metadataProvider,
_container,
_model);
Context,
Metadata,
MetadataProvider,
Container,
Model);
var results = new List<ModelValidationResult>();
for (var i = 0; i < count; i++)
@ -145,21 +145,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
for (var i = 0; i < resultsCount; 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,
// 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)
{
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
// extra entries if they don't exist already.
var entry = _modelState[_key];
var entry = ModelState[Key];
if (entry != null)
{
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();
if (model != null && !_currentPath.Push(model))
if (model != null && !CurrentPath.Push(model))
{
// This is a cycle, bail.
return true;
@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
metadata = entry?.Metadata ?? metadata;
var strategy = entry?.Strategy;
if (_modelState.HasReachedMaxErrors)
if (ModelState.HasReachedMaxErrors)
{
SuppressValidation(key);
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.
SuppressValidation(entry.Key);
_currentPath.Pop(model);
CurrentPath.Pop(model);
return true;
}
using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy))
{
if (_metadata.IsEnumerableType)
if (Metadata.IsEnumerableType)
{
return VisitComplexType(DefaultCollectionValidationStrategy.Instance);
}
if (_metadata.IsComplexType)
if (Metadata.IsComplexType)
{
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.
private bool VisitComplexType(IValidationStrategy defaultStrategy)
protected virtual bool VisitComplexType(IValidationStrategy defaultStrategy)
{
var isValid = true;
if (_model != null && _metadata.ValidateChildren)
if (Model != null && Metadata.ValidateChildren)
{
var strategy = _strategy ?? defaultStrategy;
var strategy = Strategy ?? defaultStrategy;
isValid = VisitChildren(strategy);
}
else if (_model != null)
else if (Model != null)
{
// 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
// code will set it to 'valid' or 'invalid'
SuppressValidation(_key);
SuppressValidation(Key);
}
// 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 ((isValid || ValidateComplexTypesIfChildValidationFails) && !_modelState.HasReachedMaxErrors)
if ((isValid || ValidateComplexTypesIfChildValidationFails) && !ModelState.HasReachedMaxErrors)
{
isValid &= ValidateNode();
}
@ -250,22 +250,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
return isValid;
}
private bool VisitSimpleType()
protected virtual bool VisitSimpleType()
{
if (_modelState.HasReachedMaxErrors)
if (ModelState.HasReachedMaxErrors)
{
SuppressValidation(_key);
SuppressValidation(Key);
return false;
}
return ValidateNode();
}
private bool VisitChildren(IValidationStrategy strategy)
protected virtual bool VisitChildren(IValidationStrategy strategy)
{
var isValid = true;
var enumerator = strategy.GetChildren(_metadata, _key, _model);
var parentEntry = new ValidationEntry(_metadata, _key, _model);
var enumerator = strategy.GetChildren(Metadata, Key, Model);
var parentEntry = new ValidationEntry(Metadata, Key, Model);
while (enumerator.MoveNext())
{
@ -284,7 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
return isValid;
}
private void SuppressValidation(string key)
protected virtual void SuppressValidation(string key)
{
if (key == null)
{
@ -293,26 +293,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
return;
}
var entries = _modelState.FindKeysWithPrefix(key);
var entries = ModelState.FindKeysWithPrefix(key);
foreach (var entry in entries)
{
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;
}
ValidationStateEntry entry;
_validationState.TryGetValue(model, out entry);
ValidationState.TryGetValue(model, out entry);
return entry;
}
private struct StateManager : IDisposable
protected struct StateManager : IDisposable
{
private readonly ValidationVisitor _visitor;
private readonly object _container;
@ -331,11 +331,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
var recursifier = new StateManager(visitor, model);
visitor._container = visitor._model;
visitor._key = key;
visitor._metadata = metadata;
visitor._model = model;
visitor._strategy = strategy;
visitor.Container = visitor.Model;
visitor.Key = key;
visitor.Metadata = metadata;
visitor.Model = model;
visitor.Strategy = strategy;
return recursifier;
}
@ -345,22 +345,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
_visitor = visitor;
_newModel = newModel;
_container = _visitor._container;
_key = _visitor._key;
_metadata = _visitor._metadata;
_model = _visitor._model;
_strategy = _visitor._strategy;
_container = _visitor.Container;
_key = _visitor.Key;
_metadata = _visitor.Metadata;
_model = _visitor.Model;
_strategy = _visitor.Strategy;
}
public void Dispose()
{
_visitor._container = _container;
_visitor._key = _key;
_visitor._metadata = _metadata;
_visitor._model = _model;
_visitor._strategy = _strategy;
_visitor.Container = _container;
_visitor.Key = _key;
_visitor.Metadata = _metadata;
_visitor.Model = _model;
_visitor.Strategy = _strategy;
_visitor._currentPath.Pop(_newModel);
_visitor.CurrentPath.Pop(_newModel);
}
}
}