diff --git a/src/System.Web.Http/System.Web.Http.csproj b/src/System.Web.Http/System.Web.Http.csproj index 35b9c7f05..38932329c 100644 --- a/src/System.Web.Http/System.Web.Http.csproj +++ b/src/System.Web.Http/System.Web.Http.csproj @@ -268,7 +268,9 @@ + + diff --git a/src/System.Web.Http/Validation/BodyModelValidatorContext.cs b/src/System.Web.Http/Validation/BodyModelValidatorContext.cs new file mode 100644 index 000000000..c27bbe942 --- /dev/null +++ b/src/System.Web.Http/Validation/BodyModelValidatorContext.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Web.Http.Controllers; +using System.Web.Http.Metadata; +using System.Web.Http.ModelBinding; + +namespace System.Web.Http.Validation +{ + /// + /// Context passed between methods. + /// + public class BodyModelValidatorContext + { + /// + /// Gets or sets the used to provide the model metadata. + /// + public ModelMetadataProvider MetadataProvider { get; set; } + + /// + /// Gets or sets the within which the model is being validated. + /// + public HttpActionContext ActionContext { get; set; } + + /// + /// Gets or sets the . + /// + public IModelValidatorCache ValidatorCache { get; set; } + + /// + /// Gets or sets the current . + /// + public ModelStateDictionary ModelState { get; set; } + + /// + /// Gets or sets the set of model objects visited in this validation. Includes the model being validated in the + /// current scope. + /// + public HashSet Visited { get; set; } + + /// + /// Gets or sets the stack of s used in this validation. Includes + /// the to generate model state keys for the current scope. + /// + public Stack KeyBuilders { get; set; } + + /// + /// Gets or sets the model state prefix for the root scope of this validation. + /// + public string RootPrefix { get; set; } + } +} diff --git a/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs b/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs index 60a092b8f..976247b99 100644 --- a/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs +++ b/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs @@ -16,15 +16,10 @@ namespace System.Web.Http.Validation { /// - /// Recursively validate an object. + /// Recursively validate an object. /// public class DefaultBodyModelValidator : IBodyModelValidator { - private interface IKeyBuilder - { - string AppendTo(string prefix); - } - /// /// Determines whether the is valid and adds any validation errors to the 's /// @@ -64,14 +59,14 @@ public bool Validate(object model, Type type, ModelMetadataProvider metadataProv } ModelMetadata metadata = metadataProvider.GetMetadataForType(() => model, type); - ValidationContext validationContext = new ValidationContext() + BodyModelValidatorContext validationContext = new BodyModelValidatorContext { MetadataProvider = metadataProvider, ActionContext = actionContext, ValidatorCache = actionContext.GetValidatorCache(), ModelState = actionContext.ModelState, Visited = new HashSet(ReferenceEqualityComparer.Instance), - KeyBuilders = new Stack(), + KeyBuilders = new Stack(), RootPrefix = keyPrefix }; return ValidateNodeAndChildren(metadata, validationContext, container: null, validators: null); @@ -87,12 +82,36 @@ public virtual bool ShouldValidateType(Type type) return !MediaTypeFormatterCollection.IsTypeExcludedFromValidation(type); } + /// + /// Recursively validate the given and . + /// + /// The for the object to validate. + /// The . + /// The object containing the object to validate. + /// The collection of s. + /// + /// if validation succeeds for the given , + /// , and child nodes; otherwise. + /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "See comment below")] - private bool ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, object container, IEnumerable validators) + protected virtual bool ValidateNodeAndChildren( + ModelMetadata metadata, + BodyModelValidatorContext validationContext, + object container, + IEnumerable validators) { // Recursion guard to avoid stack overflows RuntimeHelpers.EnsureSufficientExecutionStack(); + if (metadata == null) + { + throw Error.ArgumentNull("metadata"); + } + if (validationContext == null) + { + throw Error.ArgumentNull("validationContext"); + } + object model = null; try { @@ -155,8 +174,26 @@ private bool ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext v return isValid; } - private bool ValidateProperties(ModelMetadata metadata, ValidationContext validationContext) + /// + /// Recursively validate the properties of the given . + /// + /// The for the object to validate. + /// The . + /// + /// if validation succeeds for all properties in ; + /// otherwise. + /// + protected virtual bool ValidateProperties(ModelMetadata metadata, BodyModelValidatorContext validationContext) { + if (metadata == null) + { + throw Error.ArgumentNull("metadata"); + } + if (validationContext == null) + { + throw Error.ArgumentNull("validationContext"); + } + bool isValid = true; PropertyScope propertyScope = new PropertyScope(); validationContext.KeyBuilders.Push(propertyScope); @@ -172,8 +209,26 @@ private bool ValidateProperties(ModelMetadata metadata, ValidationContext valida return isValid; } - private bool ValidateElements(IEnumerable model, ValidationContext validationContext) + /// + /// Recursively validate the elements of the collection. + /// + /// The instance containing the elements to validate. + /// The . + /// + /// if validation succeeds for all elements of ; + /// otherwise. + /// + protected virtual bool ValidateElements(IEnumerable model, BodyModelValidatorContext validationContext) { + if (model == null) + { + throw Error.ArgumentNull("model"); + } + if (validationContext == null) + { + throw Error.ArgumentNull("validationContext"); + } + bool isValid = true; Type elementType = GetElementType(model.GetType()); ModelMetadata elementMetadata = validationContext.MetadataProvider.GetMetadataForType(null, elementType); @@ -207,15 +262,39 @@ private bool ValidateElements(IEnumerable model, ValidationContext validationCon return isValid; } - // Validates a single node (not including children) - // Returns true if validation passes successfully - private static bool ShallowValidate(ModelMetadata metadata, ValidationContext validationContext, object container, IEnumerable validators) + /// + /// Validate a single node, not including its children. + /// + /// The . + /// The . + /// The object to validate. + /// The collection of s. + /// + /// if validation succeeds for the given and + /// ; otherwise. + /// + protected virtual bool ShallowValidate( + ModelMetadata metadata, + BodyModelValidatorContext validationContext, + object container, + IEnumerable validators) { + if (metadata == null) + { + throw Error.ArgumentNull("metadata"); + } + if (validationContext == null) + { + throw Error.ArgumentNull("validationContext"); + } + if (validators == null) + { + throw Error.ArgumentNull("validators"); + } + bool isValid = true; string modelKey = null; - Contract.Assert(validators != null); - // When the are no validators we bail quickly. This saves a GetEnumerator allocation. // In a large array (tens of thousands or more) scenario it's very significant. ICollection validatorsAsCollection = validators as ICollection; @@ -231,7 +310,7 @@ private static bool ShallowValidate(ModelMetadata metadata, ValidationContext va if (modelKey == null) { modelKey = validationContext.RootPrefix; - foreach (IKeyBuilder keyBuilder in validationContext.KeyBuilders.Reverse()) + foreach (IBodyModelValidatorKeyBuilder keyBuilder in validationContext.KeyBuilders.Reverse()) { modelKey = keyBuilder.AppendTo(modelKey); } @@ -263,7 +342,7 @@ private static Type GetElementType(Type type) return typeof(object); } - private class PropertyScope : IKeyBuilder + private class PropertyScope : IBodyModelValidatorKeyBuilder { public string PropertyName { get; set; } @@ -273,7 +352,7 @@ public string AppendTo(string prefix) } } - private class ElementScope : IKeyBuilder + private class ElementScope : IBodyModelValidatorKeyBuilder { public int Index { get; set; } @@ -282,16 +361,5 @@ public string AppendTo(string prefix) return ModelBindingHelper.CreateIndexModelName(prefix, Index); } } - - private class ValidationContext - { - public ModelMetadataProvider MetadataProvider { get; set; } - public HttpActionContext ActionContext { get; set; } - public IModelValidatorCache ValidatorCache { get; set; } - public ModelStateDictionary ModelState { get; set; } - public HashSet Visited { get; set; } - public Stack KeyBuilders { get; set; } - public string RootPrefix { get; set; } - } } } diff --git a/src/System.Web.Http/Validation/IBodyModelValidatorKeyBuilder.cs b/src/System.Web.Http/Validation/IBodyModelValidatorKeyBuilder.cs new file mode 100644 index 000000000..a45a6b732 --- /dev/null +++ b/src/System.Web.Http/Validation/IBodyModelValidatorKeyBuilder.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Web.Http.Validation +{ + /// + /// Abstraction for creating keys used in nested validation scopes. Intended for use in + /// implementations, especially . + /// + public interface IBodyModelValidatorKeyBuilder + { + /// + /// Returns the key for a nested scope within the scope. + /// + /// Key for the current scope. + /// Key for a nested scope. Usually appends a property name to . + string AppendTo(string prefix); + } +} diff --git a/src/System.Web.Http/Validation/IModelValidatorCache.cs b/src/System.Web.Http/Validation/IModelValidatorCache.cs index f7b84e37c..81bcba823 100644 --- a/src/System.Web.Http/Validation/IModelValidatorCache.cs +++ b/src/System.Web.Http/Validation/IModelValidatorCache.cs @@ -1,16 +1,21 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Web.Http.Metadata; namespace System.Web.Http.Validation { /// - /// Defines a cache for s. This cache is keyed on the type or property that the metadata is associated with. + /// Defines a cache for s. This cache is keyed on the type or property that the + /// metadata is associated with. /// - internal interface IModelValidatorCache + public interface IModelValidatorCache { + /// + /// Returns the s for the given . + /// + /// The . + /// An array of s for the given . ModelValidator[] GetValidators(ModelMetadata metadata); } }