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> /// </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);
} }
} }
} }