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