Make subclassing `DefaultBodyModelValidator` more useful
- #9 - add `BodyModelValidatorContext` and `IBodyModelValidatorKeyBuilder`
This commit is contained in:
Родитель
2d183608fd
Коммит
eebbf38846
|
@ -268,7 +268,9 @@
|
|||
<Compile Include="Tracing\Tracers\FormatterLoggerTraceWrapper.cs" />
|
||||
<Compile Include="Tracing\Tracers\DefaultHttpControllerTypeResolverTracer.cs" />
|
||||
<Compile Include="Tracing\Tracers\OverrideFilterTracer.cs" />
|
||||
<Compile Include="Validation\BodyModelValidatorContext.cs" />
|
||||
<Compile Include="Validation\IModelValidatorCache.cs" />
|
||||
<Compile Include="Validation\IBodyModelValidatorKeyBuilder.cs" />
|
||||
<Compile Include="Validation\ModelValidatorCache.cs" />
|
||||
<Compile Include="Controllers\ResponseMessageResultConverter.cs" />
|
||||
<Compile Include="Controllers\ValueResultConverter.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Context passed between <see cref="DefaultBodyModelValidator"/> methods.
|
||||
/// </summary>
|
||||
public class BodyModelValidatorContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ModelMetadataProvider"/> used to provide the model metadata.
|
||||
/// </summary>
|
||||
public ModelMetadataProvider MetadataProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="HttpActionContext"/> within which the model is being validated.
|
||||
/// </summary>
|
||||
public HttpActionContext ActionContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IModelValidatorCache"/>.
|
||||
/// </summary>
|
||||
public IModelValidatorCache ValidatorCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current <see cref="ModelStateDictionary"/>.
|
||||
/// </summary>
|
||||
public ModelStateDictionary ModelState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the set of model objects visited in this validation. Includes the model being validated in the
|
||||
/// current scope.
|
||||
/// </summary>
|
||||
public HashSet<object> Visited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stack of <see cref="IBodyModelValidatorKeyBuilder"/>s used in this validation. Includes
|
||||
/// the <see cref="IBodyModelValidatorKeyBuilder"/> to generate model state keys for the current scope.
|
||||
/// </summary>
|
||||
public Stack<IBodyModelValidatorKeyBuilder> KeyBuilders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the model state prefix for the root scope of this validation.
|
||||
/// </summary>
|
||||
public string RootPrefix { get; set; }
|
||||
}
|
||||
}
|
|
@ -16,15 +16,10 @@ using System.Web.Http.ModelBinding;
|
|||
namespace System.Web.Http.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Recursively validate an object.
|
||||
/// Recursively validate an object.
|
||||
/// </summary>
|
||||
public class DefaultBodyModelValidator : IBodyModelValidator
|
||||
{
|
||||
private interface IKeyBuilder
|
||||
{
|
||||
string AppendTo(string prefix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the <paramref name="model"/> is valid and adds any validation errors to the <paramref name="actionContext"/>'s <see cref="ModelStateDictionary"/>
|
||||
/// </summary>
|
||||
|
@ -64,14 +59,14 @@ namespace System.Web.Http.Validation
|
|||
}
|
||||
|
||||
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<object>(ReferenceEqualityComparer.Instance),
|
||||
KeyBuilders = new Stack<IKeyBuilder>(),
|
||||
KeyBuilders = new Stack<IBodyModelValidatorKeyBuilder>(),
|
||||
RootPrefix = keyPrefix
|
||||
};
|
||||
return ValidateNodeAndChildren(metadata, validationContext, container: null, validators: null);
|
||||
|
@ -87,12 +82,36 @@ namespace System.Web.Http.Validation
|
|||
return !MediaTypeFormatterCollection.IsTypeExcludedFromValidation(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively validate the given <paramref name="metadata"/> and <paramref name="container"/>.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="ModelMetadata"/> for the object to validate.</param>
|
||||
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
|
||||
/// <param name="container">The object containing the object to validate.</param>
|
||||
/// <param name="validators">The collection of <see cref="ModelValidator"/>s.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if validation succeeds for the given <paramref name="metadata"/>,
|
||||
/// <paramref name="container"/>, and child nodes; <see langword="false"/> otherwise.
|
||||
/// </returns>
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "See comment below")]
|
||||
private bool ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, object container, IEnumerable<ModelValidator> validators)
|
||||
protected virtual bool ValidateNodeAndChildren(
|
||||
ModelMetadata metadata,
|
||||
BodyModelValidatorContext validationContext,
|
||||
object container,
|
||||
IEnumerable<ModelValidator> 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 @@ namespace System.Web.Http.Validation
|
|||
return isValid;
|
||||
}
|
||||
|
||||
private bool ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
|
||||
/// <summary>
|
||||
/// Recursively validate the properties of the given <paramref name="metadata"/>.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="ModelMetadata"/> for the object to validate.</param>
|
||||
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if validation succeeds for all properties in <paramref name="metadata"/>;
|
||||
/// <see langword="false"/> otherwise.
|
||||
/// </returns>
|
||||
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 @@ namespace System.Web.Http.Validation
|
|||
return isValid;
|
||||
}
|
||||
|
||||
private bool ValidateElements(IEnumerable model, ValidationContext validationContext)
|
||||
/// <summary>
|
||||
/// Recursively validate the elements of the <paramref name="model"/> collection.
|
||||
/// </summary>
|
||||
/// <param name="model">The <see cref="IEnumerable"/> instance containing the elements to validate.</param>
|
||||
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if validation succeeds for all elements of <paramref name="model"/>;
|
||||
/// <see langword="false"/> otherwise.
|
||||
/// </returns>
|
||||
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 @@ namespace System.Web.Http.Validation
|
|||
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<ModelValidator> validators)
|
||||
/// <summary>
|
||||
/// Validate a single node, not including its children.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
|
||||
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
|
||||
/// <param name="container">The object to validate.</param>
|
||||
/// <param name="validators">The collection of <see cref="ModelValidator"/>s.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if validation succeeds for the given <paramref name="metadata"/> and
|
||||
/// <paramref name="container"/>; <see langword="false"/> otherwise.
|
||||
/// </returns>
|
||||
protected virtual bool ShallowValidate(
|
||||
ModelMetadata metadata,
|
||||
BodyModelValidatorContext validationContext,
|
||||
object container,
|
||||
IEnumerable<ModelValidator> 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 @@ namespace System.Web.Http.Validation
|
|||
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 @@ namespace System.Web.Http.Validation
|
|||
return typeof(object);
|
||||
}
|
||||
|
||||
private class PropertyScope : IKeyBuilder
|
||||
private class PropertyScope : IBodyModelValidatorKeyBuilder
|
||||
{
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
|
@ -273,7 +352,7 @@ namespace System.Web.Http.Validation
|
|||
}
|
||||
}
|
||||
|
||||
private class ElementScope : IKeyBuilder
|
||||
private class ElementScope : IBodyModelValidatorKeyBuilder
|
||||
{
|
||||
public int Index { get; set; }
|
||||
|
||||
|
@ -282,16 +361,5 @@ namespace System.Web.Http.Validation
|
|||
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<object> Visited { get; set; }
|
||||
public Stack<IKeyBuilder> KeyBuilders { get; set; }
|
||||
public string RootPrefix { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for creating keys used in nested validation scopes. Intended for use in
|
||||
/// <see cref="IBodyModelValidator"/> implementations, especially <see cref="DefaultBodyModelValidator"/>.
|
||||
/// </summary>
|
||||
public interface IBodyModelValidatorKeyBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the key for a nested scope within the <paramref name="prefix"/> scope.
|
||||
/// </summary>
|
||||
/// <param name="prefix">Key for the current scope.</param>
|
||||
/// <returns>Key for a nested scope. Usually appends a property name to <paramref name="prefix"/>.</returns>
|
||||
string AppendTo(string prefix);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a cache for <see cref="ModelValidator"/>s. This cache is keyed on the type or property that the metadata is associated with.
|
||||
/// Defines a cache for <see cref="ModelValidator"/>s. This cache is keyed on the type or property that the
|
||||
/// metadata is associated with.
|
||||
/// </summary>
|
||||
internal interface IModelValidatorCache
|
||||
public interface IModelValidatorCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ModelValidator"/>s for the given <paramref name="metadata"/>.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
|
||||
/// <returns>An array of <see cref="ModelValidator"/>s for the given <paramref name="metadata"/>.</returns>
|
||||
ModelValidator[] GetValidators(ModelMetadata metadata);
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче