Restoring modelvalidation node.
This commit is contained in:
parent
8b5223518f
commit
22f1881cc6
|
|
@ -16,10 +16,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// <param name="isModelSet">A value that represents if the model has been set by the
|
||||
/// <see cref="IModelBinder"/>.</param>
|
||||
public ModelBindingResult(object model, string key, bool isModelSet)
|
||||
: this (model, key, isModelSet, validationNode: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ModelBindingResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="model">The model which was created by the <see cref="IModelBinder"/>.</param>
|
||||
/// <param name="key">The key using which was used to attempt binding the model.</param>
|
||||
/// <param name="isModelSet">A value that represents if the model has been set by the
|
||||
/// <see cref="IModelBinder"/>.</param>
|
||||
/// <param name="validationNode">A <see cref="ModelValidationNode"/> which captures the validation information.
|
||||
/// </param>
|
||||
public ModelBindingResult(object model, string key, bool isModelSet, ModelValidationNode validationNode)
|
||||
{
|
||||
Model = model;
|
||||
Key = key;
|
||||
IsModelSet = isModelSet;
|
||||
ValidationNode = validationNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -47,5 +62,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// </para>
|
||||
/// </summary>
|
||||
public bool IsModelSet { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ModelValidationNode"/> associated with the current <see cref="ModelBindingResult"/>.
|
||||
/// </summary>
|
||||
public ModelValidationNode ValidationNode { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
// 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 Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Captures the validation information for a particular model.
|
||||
/// </summary>
|
||||
public class ModelValidationNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ModelValidationNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key that will be used by the validation system to find <see cref="ModelState"/>
|
||||
/// entries.</param>
|
||||
/// <param name="modelMetadata">The <see cref="ModelMetadata"/> for the <paramref name="model"/>.</param>
|
||||
/// <param name="model">The model object which is to be validated.</param>
|
||||
public ModelValidationNode([NotNull] string key, [NotNull] ModelMetadata modelMetadata, object model)
|
||||
: this (key, modelMetadata, model, new List<ModelValidationNode>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ModelValidationNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">The key that will be used by the validation system to add
|
||||
/// <see cref="ModelStateDictionary"/> entries.</param>
|
||||
/// <param name="modelMetadata">The <see cref="ModelMetadata"/> for the <paramref name="model"/>.</param>
|
||||
/// <param name="model">The model object which will be validated.</param>
|
||||
/// <param name="childNodes">A collection of child nodes.</param>
|
||||
public ModelValidationNode(
|
||||
[NotNull] string key,
|
||||
[NotNull] ModelMetadata modelMetadata,
|
||||
object model,
|
||||
[NotNull] IList<ModelValidationNode> childNodes)
|
||||
{
|
||||
Key = key;
|
||||
ModelMetadata = modelMetadata;
|
||||
ChildNodes = childNodes;
|
||||
Model = model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key used for adding <see cref="ModelStateDictionary"/> entries.
|
||||
/// </summary>
|
||||
public string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ModelMetadata"/>.
|
||||
/// </summary>
|
||||
public ModelMetadata ModelMetadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model instance which is to be validated.
|
||||
/// </summary>
|
||||
public object Model { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the child nodes.
|
||||
/// </summary>
|
||||
public IList<ModelValidationNode> ChildNodes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether all properties of the model should be validated.
|
||||
/// </summary>
|
||||
public bool ValidateAllProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether validation should be suppressed.
|
||||
/// </summary>
|
||||
public bool SuppressValidation { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,23 +10,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
public ModelValidationContext(
|
||||
[NotNull] ModelBindingContext bindingContext,
|
||||
[NotNull] ModelExplorer modelExplorer)
|
||||
: this(bindingContext.ModelName,
|
||||
bindingContext.BindingSource,
|
||||
bindingContext.OperationBindingContext.ValidatorProvider,
|
||||
bindingContext.ModelState,
|
||||
modelExplorer)
|
||||
: this(
|
||||
bindingContext.BindingSource,
|
||||
bindingContext.OperationBindingContext.ValidatorProvider,
|
||||
bindingContext.ModelState,
|
||||
modelExplorer)
|
||||
{
|
||||
}
|
||||
|
||||
public ModelValidationContext(
|
||||
string rootPrefix,
|
||||
BindingSource bindingSource,
|
||||
[NotNull] IModelValidatorProvider validatorProvider,
|
||||
[NotNull] ModelStateDictionary modelState,
|
||||
[NotNull] ModelExplorer modelExplorer)
|
||||
{
|
||||
ModelState = modelState;
|
||||
RootPrefix = rootPrefix;
|
||||
ValidatorProvider = validatorProvider;
|
||||
ModelExplorer = modelExplorer;
|
||||
BindingSource = bindingSource;
|
||||
|
|
@ -45,7 +43,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
[NotNull] ModelExplorer modelExplorer)
|
||||
{
|
||||
return new ModelValidationContext(
|
||||
parentContext.RootPrefix,
|
||||
modelExplorer.Metadata.BindingSource,
|
||||
parentContext.ValidatorProvider,
|
||||
parentContext.ModelState,
|
||||
|
|
@ -56,8 +53,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
|
||||
public ModelStateDictionary ModelState { get; }
|
||||
|
||||
public string RootPrefix { get; set; }
|
||||
|
||||
public BindingSource BindingSource { get; set; }
|
||||
|
||||
public IModelValidatorProvider ValidatorProvider { get; }
|
||||
|
|
|
|||
|
|
@ -1336,13 +1336,17 @@ namespace Microsoft.AspNet.Mvc
|
|||
modelName);
|
||||
|
||||
var validationContext = new ModelValidationContext(
|
||||
modelName,
|
||||
bindingSource: null,
|
||||
validatorProvider: BindingContext.ValidatorProvider,
|
||||
modelState: ModelState,
|
||||
modelExplorer: modelExplorer);
|
||||
|
||||
ObjectValidator.Validate(validationContext);
|
||||
ObjectValidator.Validate(
|
||||
validationContext,
|
||||
new ModelValidationNode(modelName, modelExplorer.Metadata, model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
});
|
||||
return ModelState.IsValid;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,12 +90,11 @@ namespace Microsoft.AspNet.Mvc
|
|||
modelBindingResult.Model);
|
||||
|
||||
var validationContext = new ModelValidationContext(
|
||||
key,
|
||||
modelBindingContext.BindingSource,
|
||||
operationContext.ValidatorProvider,
|
||||
modelState,
|
||||
modelExplorer);
|
||||
_validator.Validate(validationContext);
|
||||
_validator.Validate(validationContext, modelBindingResult.ValidationNode);
|
||||
}
|
||||
|
||||
return modelBindingResult;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var result = await modelBinder.BindModelAsync(bindingContext);
|
||||
|
||||
var modelBindingResult = result != null ?
|
||||
new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
|
||||
new ModelBindingResult(result.Model, result.Key, result.IsModelSet, result.ValidationNode) :
|
||||
new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
||||
|
||||
// A model binder was specified by metadata and this binder handles all such cases.
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var modelBindingResult =
|
||||
result != null ?
|
||||
new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
|
||||
new ModelBindingResult(result.Model, result.Key, result.IsModelSet, result.ValidationNode) :
|
||||
new ModelBindingResult(model: null, key: context.ModelName, isModelSet: false);
|
||||
|
||||
// This model binder is the only handler for its binding source.
|
||||
|
|
|
|||
|
|
@ -54,11 +54,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||
|
||||
// For compatibility with MVC 5.0 for top level object we want to consider an empty key instead of
|
||||
// For compatibility with MVC 5.0 for top level object we want to consider an empty key instead of
|
||||
// the parameter name/a custom name. In all other cases (like when binding body to a property) we
|
||||
// consider the entire ModelName as a prefix.
|
||||
var modelBindingKey = isTopLevelObject ? string.Empty : bindingContext.ModelName;
|
||||
return new ModelBindingResult(model, key: modelBindingKey, isModelSet: true);
|
||||
|
||||
var validationNode = new ModelValidationNode(modelBindingKey, bindingContext.ModelMetadata, model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
return new ModelBindingResult(
|
||||
model,
|
||||
key: modelBindingKey,
|
||||
isModelSet: true,
|
||||
validationNode: validationNode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,7 +39,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
try
|
||||
{
|
||||
var model = Convert.FromBase64String(value);
|
||||
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
||||
|
||||
// We do not need to set an explict ModelValidationNode since CompositeModelBinder does that automatically.
|
||||
return new ModelBindingResult(
|
||||
model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,16 +31,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
|
||||
|
||||
IEnumerable<TElement> boundCollection;
|
||||
CollectionResult result;
|
||||
if (valueProviderResult == null)
|
||||
{
|
||||
boundCollection = await BindComplexCollection(bindingContext);
|
||||
result = await BindComplexCollection(bindingContext);
|
||||
boundCollection = result.Model;
|
||||
}
|
||||
else
|
||||
{
|
||||
boundCollection = await BindSimpleCollection(
|
||||
result = await BindSimpleCollection(
|
||||
bindingContext,
|
||||
valueProviderResult.RawValue,
|
||||
valueProviderResult.Culture);
|
||||
boundCollection = result.Model;
|
||||
}
|
||||
|
||||
var model = bindingContext.Model;
|
||||
|
|
@ -54,12 +57,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
CopyToModel(model, boundCollection);
|
||||
}
|
||||
|
||||
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
||||
return new ModelBindingResult(
|
||||
model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true,
|
||||
validationNode: result?.ValidationNode);
|
||||
}
|
||||
|
||||
// Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value
|
||||
// is [ "1", "2" ] and needs to be converted to an int[].
|
||||
internal async Task<IEnumerable<TElement>> BindSimpleCollection(
|
||||
internal async Task<CollectionResult> BindSimpleCollection(
|
||||
ModelBindingContext bindingContext,
|
||||
object rawValue,
|
||||
CultureInfo culture)
|
||||
|
|
@ -74,6 +81,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
|
||||
var elementMetadata = metadataProvider.GetMetadataForType(typeof(TElement));
|
||||
|
||||
var validationNode = new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
boundCollection);
|
||||
var rawValueArray = RawValueToObjectArray(rawValue);
|
||||
foreach (var rawValueElement in rawValueArray)
|
||||
{
|
||||
|
|
@ -91,18 +102,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
object boundValue = null;
|
||||
var result =
|
||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext);
|
||||
if (result != null)
|
||||
if (result != null && result.IsModelSet)
|
||||
{
|
||||
boundValue = result.Model;
|
||||
if (result.ValidationNode != null)
|
||||
{
|
||||
validationNode.ChildNodes.Add(result.ValidationNode);
|
||||
}
|
||||
}
|
||||
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
|
||||
}
|
||||
|
||||
return boundCollection;
|
||||
return new CollectionResult
|
||||
{
|
||||
ValidationNode = validationNode,
|
||||
Model = boundCollection
|
||||
};
|
||||
}
|
||||
|
||||
// Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1].
|
||||
private async Task<IEnumerable<TElement>> BindComplexCollection(ModelBindingContext bindingContext)
|
||||
private async Task<CollectionResult> BindComplexCollection(ModelBindingContext bindingContext)
|
||||
{
|
||||
var indexPropertyName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "index");
|
||||
var valueProviderResultIndex = await bindingContext.ValueProvider.GetValueAsync(indexPropertyName);
|
||||
|
|
@ -111,7 +130,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return await BindComplexCollectionFromIndexes(bindingContext, indexNames);
|
||||
}
|
||||
|
||||
internal async Task<IEnumerable<TElement>> BindComplexCollectionFromIndexes(
|
||||
internal async Task<CollectionResult> BindComplexCollectionFromIndexes(
|
||||
ModelBindingContext bindingContext,
|
||||
IEnumerable<string> indexNames)
|
||||
{
|
||||
|
|
@ -131,6 +150,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var elementMetadata = metadataProvider.GetMetadataForType(typeof(TElement));
|
||||
|
||||
var boundCollection = new List<TElement>();
|
||||
var validationNode = new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
boundCollection);
|
||||
foreach (var indexName in indexNames)
|
||||
{
|
||||
var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName);
|
||||
|
|
@ -146,10 +169,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var result =
|
||||
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);
|
||||
if (result != null)
|
||||
if (result != null && result.IsModelSet)
|
||||
{
|
||||
didBind = true;
|
||||
boundValue = result.Model;
|
||||
if (result.ValidationNode != null)
|
||||
{
|
||||
validationNode.ChildNodes.Add(result.ValidationNode);
|
||||
}
|
||||
}
|
||||
|
||||
// infinite size collection stops on first bind failure
|
||||
|
|
@ -161,7 +188,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
|
||||
}
|
||||
|
||||
return boundCollection;
|
||||
return new CollectionResult
|
||||
{
|
||||
ValidationNode = validationNode,
|
||||
Model = boundCollection
|
||||
};
|
||||
}
|
||||
|
||||
internal class CollectionResult
|
||||
{
|
||||
public ModelValidationNode ValidationNode { get; set; }
|
||||
|
||||
public IEnumerable<TElement> Model { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
bindingContext.OperationBindingContext.BodyBindingState =
|
||||
newBindingContext.OperationBindingContext.BodyBindingState;
|
||||
|
||||
var bindingKey = bindingContext.ModelName;
|
||||
if (modelBindingResult.IsModelSet)
|
||||
{
|
||||
// Update the model state key if we are bound using an empty prefix and it is a complex type.
|
||||
|
|
@ -78,15 +79,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// In this case, for the model parameter the key would be SimpleType instead of model.SimpleType.
|
||||
// (i.e here the prefix for the model key is empty).
|
||||
// For the id parameter the key would be id.
|
||||
return modelBindingResult;
|
||||
bindingKey = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through to update the ModelBindingResult's key.
|
||||
// Update the model validation node if the model binding result was set but no validation node was provided.
|
||||
// This would typically be the case where leaf level model binders, do not have to add a validation node
|
||||
// for validation to take effect. The composite being the entry point for model binders, takes care or
|
||||
// adding missing validation nodes.
|
||||
var modelValidationNode = modelBindingResult.ValidationNode;
|
||||
if (modelBindingResult.IsModelSet && modelValidationNode == null)
|
||||
{
|
||||
modelValidationNode = new ModelValidationNode(
|
||||
bindingKey,
|
||||
bindingContext.ModelMetadata,
|
||||
modelBindingResult.Model);
|
||||
}
|
||||
|
||||
return new ModelBindingResult(
|
||||
modelBindingResult.Model,
|
||||
bindingContext.ModelName,
|
||||
modelBindingResult.IsModelSet);
|
||||
bindingKey,
|
||||
modelBindingResult.IsModelSet,
|
||||
modelValidationNode);
|
||||
}
|
||||
|
||||
private async Task<ModelBindingResult> TryBind(ModelBindingContext bindingContext)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var result = await binder.BindModelAsync(bindingContext);
|
||||
|
||||
var modelBindingResult = result != null ?
|
||||
new ModelBindingResult(result.Model, result.Key, result.IsModelSet) :
|
||||
new ModelBindingResult(result.Model, result.Key, result.IsModelSet, result.ValidationNode) :
|
||||
new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
|
||||
|
||||
// Were able to resolve a binder type.
|
||||
|
|
|
|||
|
|
@ -51,7 +51,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, isModelSet: model != null));
|
||||
ModelValidationNode validationNode = null;
|
||||
if (model != null)
|
||||
{
|
||||
validationNode = new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
model);
|
||||
}
|
||||
|
||||
return Task.FromResult(
|
||||
new ModelBindingResult(
|
||||
model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: model != null,
|
||||
validationNode: validationNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
typeof(KeyValuePair<TKey, TValue>),
|
||||
allowNullModel: true);
|
||||
|
||||
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key");
|
||||
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value");
|
||||
var childNodes = new List<ModelValidationNode>();
|
||||
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key", childNodes);
|
||||
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value", childNodes);
|
||||
|
||||
if (keyResult.IsModelSet && valueResult.IsModelSet)
|
||||
{
|
||||
|
|
@ -24,8 +25,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
ModelBindingHelper.CastOrDefault<TKey>(keyResult.Model),
|
||||
ModelBindingHelper.CastOrDefault<TValue>(valueResult.Model));
|
||||
|
||||
// Update the model for the top level validation node.
|
||||
var modelValidationNode =
|
||||
new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
model,
|
||||
childNodes);
|
||||
|
||||
// Success
|
||||
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
||||
return new ModelBindingResult(
|
||||
model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true,
|
||||
validationNode: modelValidationNode);
|
||||
}
|
||||
else if (!keyResult.IsModelSet && valueResult.IsModelSet)
|
||||
{
|
||||
|
|
@ -55,8 +68,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(ModelBindingContext parentBindingContext,
|
||||
string propertyName)
|
||||
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(
|
||||
ModelBindingContext parentBindingContext,
|
||||
string propertyName,
|
||||
List<ModelValidationNode> childNodes)
|
||||
{
|
||||
var propertyModelMetadata =
|
||||
parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel));
|
||||
|
|
@ -72,6 +87,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
propertyBindingContext);
|
||||
if (modelBindingResult != null)
|
||||
{
|
||||
if (modelBindingResult.ValidationNode != null)
|
||||
{
|
||||
childNodes.Add(modelBindingResult.ValidationNode);
|
||||
}
|
||||
|
||||
return modelBindingResult;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,12 +44,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
EnsureModel(bindingContext);
|
||||
var result = await CreateAndPopulateDto(bindingContext, mutableObjectBinderContext.PropertyMetadata);
|
||||
|
||||
var validationNode = new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
bindingContext.Model);
|
||||
|
||||
// post-processing, e.g. property setters and hooking up validation
|
||||
ProcessDto(bindingContext, (ComplexModelDto)result.Model);
|
||||
ProcessDto(bindingContext, (ComplexModelDto)result.Model, validationNode);
|
||||
return new ModelBindingResult(
|
||||
bindingContext.Model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true);
|
||||
isModelSet: true,
|
||||
validationNode: validationNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -359,11 +365,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return validationInfo;
|
||||
}
|
||||
|
||||
internal void ProcessDto(ModelBindingContext bindingContext, ComplexModelDto dto)
|
||||
// Internal for testing.
|
||||
internal ModelValidationNode ProcessDto(
|
||||
ModelBindingContext bindingContext,
|
||||
ComplexModelDto dto,
|
||||
ModelValidationNode validationNode)
|
||||
{
|
||||
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model);
|
||||
|
||||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||
|
||||
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
||||
|
|
@ -415,8 +424,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
out requiredValidator);
|
||||
|
||||
SetProperty(bindingContext, modelExplorer, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
var dtoValidationNode = dtoResult.ValidationNode;
|
||||
if (dtoValidationNode == null)
|
||||
{
|
||||
// Make sure that irrespective of if the properties of the model were bound with a value,
|
||||
// create a validation node so that these get validated.
|
||||
dtoValidationNode = new ModelValidationNode(dtoResult.Key, entry.Key, dtoResult.Model);
|
||||
}
|
||||
|
||||
validationNode.ChildNodes.Add(dtoValidationNode);
|
||||
}
|
||||
}
|
||||
|
||||
return validationNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
|
||||
var model = requestServices.GetRequiredService(bindingContext.ModelType);
|
||||
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true));
|
||||
var validationNode =
|
||||
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model)
|
||||
{
|
||||
SuppressValidation = true
|
||||
};
|
||||
|
||||
return Task.FromResult(new ModelBindingResult(
|
||||
model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true,
|
||||
validationNode: validationNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
newModel = valueProviderResult.ConvertTo(bindingContext.ModelType);
|
||||
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref newModel);
|
||||
return new ModelBindingResult(newModel, bindingContext.ModelName, isModelSet: true);
|
||||
|
||||
// We do not need to set an explict ModelValidationNode since CompositeModelBinder does that automatically.
|
||||
return new ModelBindingResult(
|
||||
newModel,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||
var model = valueProviderResult.RawValue;
|
||||
ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, ref model);
|
||||
return new ModelBindingResult(model, bindingContext.ModelName, isModelSet: true);
|
||||
|
||||
// We do not need to set an explict ModelValidationNode since CompositeModelBinder does that automatically.
|
||||
return new ModelBindingResult(
|
||||
model,
|
||||
bindingContext.ModelName,
|
||||
isModelSet: true);
|
||||
}
|
||||
|
||||
internal static async Task<ValueProviderResult> GetCompatibleValueProviderResult(ModelBindingContext context)
|
||||
|
|
|
|||
|
|
@ -35,16 +35,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Validate([NotNull] ModelValidationContext modelValidationContext)
|
||||
public void Validate(
|
||||
[NotNull] ModelValidationContext modelValidationContext,
|
||||
[NotNull] ModelValidationNode validationNode)
|
||||
{
|
||||
var validationContext = new ValidationContext()
|
||||
{
|
||||
ModelValidationContext = modelValidationContext,
|
||||
Visited = new HashSet<object>(ReferenceEqualityComparer.Instance),
|
||||
ValidationNode = validationNode
|
||||
};
|
||||
|
||||
ValidateNonVisitedNodeAndChildren(
|
||||
modelValidationContext.RootPrefix,
|
||||
validationNode.Key,
|
||||
validationContext,
|
||||
validators: null);
|
||||
}
|
||||
|
|
@ -54,19 +57,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
ValidationContext validationContext,
|
||||
IList<IModelValidator> validators)
|
||||
{
|
||||
var modelValidationContext = validationContext.ModelValidationContext;
|
||||
var modelExplorer = modelValidationContext.ModelExplorer;
|
||||
|
||||
// Recursion guard to avoid stack overflows
|
||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||
|
||||
var modelValidationContext = validationContext.ModelValidationContext;
|
||||
var modelExplorer = modelValidationContext.ModelExplorer;
|
||||
var modelState = modelValidationContext.ModelState;
|
||||
|
||||
var bindingSource = modelValidationContext.BindingSource;
|
||||
if (bindingSource != null && !bindingSource.IsFromRequest)
|
||||
var currentValidationNode = validationContext.ValidationNode;
|
||||
if (currentValidationNode.SuppressValidation)
|
||||
{
|
||||
// Short circuit if the metadata represents something that was not bound using request data.
|
||||
// For example model bound using [FromServices]. Treat such objects as skipped.
|
||||
// Short circuit if the node is marked to be suppressed
|
||||
var validationState = modelState.GetFieldValidationState(modelKey);
|
||||
if (validationState == ModelValidationState.Unvalidated)
|
||||
{
|
||||
|
|
@ -88,22 +88,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
{
|
||||
// The validators are not null in the case of validating an array. Since the validators are
|
||||
// the same for all the elements of the array, we do not do GetValidators for each element,
|
||||
// instead we just pass them over. See ValidateElements function.
|
||||
var validatorProvider = modelValidationContext.ValidatorProvider;
|
||||
var validatorProviderContext = new ModelValidatorProviderContext(modelExplorer.Metadata);
|
||||
validatorProvider.GetValidators(validatorProviderContext);
|
||||
|
||||
validators = validatorProviderContext.Validators;
|
||||
// instead we just pass them over.
|
||||
validators = GetValidators(modelValidationContext.ValidatorProvider, modelExplorer.Metadata);
|
||||
}
|
||||
|
||||
// We don't need to recursively traverse the graph for null values
|
||||
if (modelExplorer.Model == null)
|
||||
// We don't need to recursively traverse the graph if there are no child nodes.
|
||||
if (currentValidationNode.ChildNodes.Count == 0 && !currentValidationNode.ValidateAllProperties)
|
||||
{
|
||||
return ShallowValidate(modelKey, modelExplorer, validationContext, validators);
|
||||
}
|
||||
|
||||
// We don't need to recursively traverse the graph for types that shouldn't be validated
|
||||
var modelType = modelExplorer.Model.GetType();
|
||||
var modelType = modelExplorer.ModelType;
|
||||
if (IsTypeExcludedFromValidation(_excludeFilters, modelType))
|
||||
{
|
||||
var result = ShallowValidate(modelKey, modelExplorer, validationContext, validators);
|
||||
|
|
@ -112,24 +108,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
}
|
||||
|
||||
// Check to avoid infinite recursion. This can happen with cycles in an object graph.
|
||||
// Note that this is only applicable in case the model is pre-existing (like in case of TryUpdateModel).
|
||||
if (validationContext.Visited.Contains(modelExplorer.Model))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
validationContext.Visited.Add(modelExplorer.Model);
|
||||
|
||||
// Validate the children first - depth-first traversal
|
||||
var enumerableModel = modelExplorer.Model as IEnumerable;
|
||||
if (enumerableModel == null)
|
||||
{
|
||||
isValid = ValidateProperties(modelKey, modelExplorer, validationContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
isValid = ValidateElements(modelKey, enumerableModel, validationContext);
|
||||
}
|
||||
|
||||
isValid = ValidateChildNodes(modelKey, modelExplorer, validationContext);
|
||||
if (isValid)
|
||||
{
|
||||
// Don't bother to validate this node if children failed.
|
||||
|
|
@ -166,32 +152,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
}
|
||||
}
|
||||
|
||||
private bool ValidateProperties(
|
||||
private IList<IModelValidator> GetValidators(IModelValidatorProvider provider, ModelMetadata metadata)
|
||||
{
|
||||
var validatorProviderContext = new ModelValidatorProviderContext(metadata);
|
||||
provider.GetValidators(validatorProviderContext);
|
||||
return validatorProviderContext.Validators;
|
||||
}
|
||||
|
||||
private bool ValidateChildNodes(
|
||||
string currentModelKey,
|
||||
ModelExplorer modelExplorer,
|
||||
ValidationContext validationContext)
|
||||
{
|
||||
var isValid = true;
|
||||
ExpandValidationNode(validationContext, modelExplorer);
|
||||
|
||||
foreach (var property in modelExplorer.Metadata.Properties)
|
||||
IList<IModelValidator> validators = null;
|
||||
if (modelExplorer.Metadata.IsCollectionType && modelExplorer.Model != null)
|
||||
{
|
||||
var propertyExplorer = modelExplorer.GetExplorerForProperty(property.PropertyName);
|
||||
var propertyMetadata = propertyExplorer.Metadata;
|
||||
var enumerableModel = (IEnumerable)modelExplorer.Model;
|
||||
var elementType = GetElementType(enumerableModel.GetType());
|
||||
var elementMetadata = _modelMetadataProvider.GetMetadataForType(elementType);
|
||||
validators = GetValidators(validationContext.ModelValidationContext.ValidatorProvider, elementMetadata);
|
||||
}
|
||||
|
||||
foreach (var childNode in validationContext.ValidationNode.ChildNodes)
|
||||
{
|
||||
var childModelExplorer = childNode.ModelMetadata.MetadataKind == Metadata.ModelMetadataKind.Type ?
|
||||
_modelMetadataProvider.GetModelExplorerForType(childNode.ModelMetadata.ModelType, childNode.Model) :
|
||||
modelExplorer.GetExplorerForProperty(childNode.ModelMetadata.PropertyName);
|
||||
|
||||
var propertyValidationContext = new ValidationContext()
|
||||
{
|
||||
ModelValidationContext = ModelValidationContext.GetChildValidationContext(
|
||||
validationContext.ModelValidationContext,
|
||||
propertyExplorer),
|
||||
Visited = validationContext.Visited
|
||||
childModelExplorer),
|
||||
Visited = validationContext.Visited,
|
||||
ValidationNode = childNode
|
||||
};
|
||||
|
||||
var propertyBindingName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName;
|
||||
var childKey = ModelNames.CreatePropertyModelName(currentModelKey, propertyBindingName);
|
||||
|
||||
if (!ValidateNonVisitedNodeAndChildren(
|
||||
childKey,
|
||||
propertyValidationContext,
|
||||
validators: null))
|
||||
childNode.Key,
|
||||
propertyValidationContext,
|
||||
validators))
|
||||
{
|
||||
isValid = false;
|
||||
}
|
||||
|
|
@ -200,51 +203,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
return isValid;
|
||||
}
|
||||
|
||||
private bool ValidateElements(string currentKey, IEnumerable model, ValidationContext validationContext)
|
||||
{
|
||||
var elementType = GetElementType(model.GetType());
|
||||
var elementMetadata = _modelMetadataProvider.GetMetadataForType(elementType);
|
||||
|
||||
var validatorProvider = validationContext.ModelValidationContext.ValidatorProvider;
|
||||
var validatorProviderContext = new ModelValidatorProviderContext(elementMetadata);
|
||||
validatorProvider.GetValidators(validatorProviderContext);
|
||||
|
||||
var validators = validatorProviderContext.Validators;
|
||||
|
||||
// If there are no validators or the object is null we bail out quickly
|
||||
// when there are large arrays of null, this will save a significant amount of processing
|
||||
// with minimal impact to other scenarios.
|
||||
var anyValidatorsDefined = validators.Any();
|
||||
var index = 0;
|
||||
var isValid = true;
|
||||
foreach (var element in model)
|
||||
{
|
||||
// If the element is non null, the recursive calls might find more validators.
|
||||
// If it's null, then a shallow validation will be performed.
|
||||
if (element != null || anyValidatorsDefined)
|
||||
{
|
||||
var elementExplorer = new ModelExplorer(_modelMetadataProvider, elementMetadata, element);
|
||||
var elementKey = ModelNames.CreateIndexModelName(currentKey, index);
|
||||
var elementValidationContext = new ValidationContext()
|
||||
{
|
||||
ModelValidationContext = ModelValidationContext.GetChildValidationContext(
|
||||
validationContext.ModelValidationContext,
|
||||
elementExplorer),
|
||||
Visited = validationContext.Visited
|
||||
};
|
||||
|
||||
if (!ValidateNonVisitedNodeAndChildren(elementKey, elementValidationContext, validators))
|
||||
{
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Validates a single node (not including children)
|
||||
// Returns true if validation passes successfully
|
||||
private static bool ShallowValidate(
|
||||
|
|
@ -312,6 +270,54 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
return filters.Any(filter => filter.IsTypeExcluded(type));
|
||||
}
|
||||
|
||||
private void ExpandValidationNode(ValidationContext context, ModelExplorer modelExplorer)
|
||||
{
|
||||
var validationNode = context.ValidationNode;
|
||||
if (validationNode.ChildNodes.Count != 0 ||
|
||||
!validationNode.ValidateAllProperties ||
|
||||
validationNode.Model == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modelExplorer.Metadata.IsCollectionType)
|
||||
{
|
||||
foreach (var property in validationNode.ModelMetadata.Properties)
|
||||
{
|
||||
var propertyExplorer = modelExplorer.GetExplorerForProperty(property.PropertyName);
|
||||
var propertyBindingName = property.BinderModelName ?? property.PropertyName;
|
||||
var childKey = ModelNames.CreatePropertyModelName(validationNode.Key, propertyBindingName);
|
||||
var childNode = new ModelValidationNode(childKey, property, propertyExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
validationNode.ChildNodes.Add(childNode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var enumerableModel = (IEnumerable)modelExplorer.Model;
|
||||
var elementType = GetElementType(enumerableModel.GetType());
|
||||
var elementMetadata = _modelMetadataProvider.GetMetadataForType(elementType);
|
||||
|
||||
// An integer index is incorrect in scenarios where there is a custom index provided by the user.
|
||||
// However those scenarios are supported by createing a ModelValidationNode with the right keys.
|
||||
var index = 0;
|
||||
foreach (var element in enumerableModel)
|
||||
{
|
||||
var elementExplorer = new ModelExplorer(_modelMetadataProvider, elementMetadata, element);
|
||||
var elementKey = ModelNames.CreateIndexModelName(validationNode.Key, index);
|
||||
var childNode = new ModelValidationNode(elementKey, elementMetadata, elementExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
validationNode.ChildNodes.Add(childNode);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type GetElementType(Type type)
|
||||
{
|
||||
Debug.Assert(typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()));
|
||||
|
|
@ -337,6 +343,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
public ModelValidationContext ModelValidationContext { get; set; }
|
||||
|
||||
public HashSet<object> Visited { get; set; }
|
||||
|
||||
public ModelValidationNode ValidationNode { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
/// </summary>
|
||||
/// <param name="validationContext">The <see cref="ModelValidationContext"/> associated with the current call.
|
||||
/// </param>
|
||||
void Validate(ModelValidationContext validationContext);
|
||||
/// <param name="validationNode">The <see cref="ModelValidationNode"/> for the model which gets validated.
|
||||
/// </param>
|
||||
void Validate(ModelValidationContext validationContext, ModelValidationNode validationNode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,8 +312,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, modelBindingResult.Model);
|
||||
var modelValidationContext = new ModelValidationContext(modelBindingContext, modelExplorer);
|
||||
modelValidationContext.RootPrefix = prefix;
|
||||
objectModelValidator.Validate(modelValidationContext);
|
||||
objectModelValidator.Validate(
|
||||
modelValidationContext,
|
||||
new ModelValidationNode(prefix, modelBindingContext.ModelMetadata, modelBindingResult.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
});
|
||||
return modelState.IsValid;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -417,13 +417,17 @@ namespace System.Web.Http
|
|||
var modelExplorer = MetadataProvider.GetModelExplorerForType(typeof(TEntity), entity);
|
||||
|
||||
var modelValidationContext = new ModelValidationContext(
|
||||
keyPrefix,
|
||||
bindingSource: null,
|
||||
validatorProvider: BindingContext.ValidatorProvider,
|
||||
modelState: ModelState,
|
||||
modelExplorer: modelExplorer);
|
||||
|
||||
ObjectValidator.Validate(modelValidationContext);
|
||||
ObjectValidator.Validate(
|
||||
modelValidationContext,
|
||||
new ModelValidationNode(keyPrefix, modelExplorer.Metadata, entity)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var bindingContext = GetBindingContext(typeof(Person), binderType: typeof(TrueModelBinder));
|
||||
|
||||
var model = new Person();
|
||||
var innerModelBinder = new TrueModelBinder();
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton(typeof(IModelBinder))
|
||||
.BuildServiceProvider();
|
||||
|
|
@ -67,6 +66,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var p = (Person)binderResult.Model;
|
||||
Assert.Equal(model.Age, p.Age);
|
||||
Assert.Equal(model.Name, p.Name);
|
||||
Assert.NotNull(binderResult.ValidationNode);
|
||||
Assert.Equal(bindingContext.ModelName, binderResult.ValidationNode.Key);
|
||||
Assert.Same(binderResult.Model, binderResult.ValidationNode.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -138,7 +140,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
return Task.FromResult(new ModelBindingResult(_model, bindingContext.ModelName, true));
|
||||
var validationNode =
|
||||
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, _model);
|
||||
return Task.FromResult(new ModelBindingResult(_model, bindingContext.ModelName, true, validationNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
mockInputFormatter.Verify(v => v.ReadAsync(It.IsAny<InputFormatterContext>()), Times.Once);
|
||||
Assert.NotNull(binderResult);
|
||||
Assert.True(binderResult.IsModelSet);
|
||||
Assert.NotNull(binderResult.ValidationNode);
|
||||
Assert.True(binderResult.ValidationNode.ValidateAllProperties);
|
||||
Assert.False(binderResult.ValidationNode.SuppressValidation);
|
||||
Assert.Empty(binderResult.ValidationNode.ChildNodes);
|
||||
Assert.Equal(binderResult.Key, binderResult.ValidationNode.Key);
|
||||
Assert.Equal(bindingContext.ModelMetadata, binderResult.ValidationNode.ModelMetadata);
|
||||
Assert.Same(binderResult.Model, binderResult.ValidationNode.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -71,6 +78,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Returns true because it understands the metadata type.
|
||||
Assert.NotNull(binderResult);
|
||||
Assert.False(binderResult.IsModelSet);
|
||||
Assert.Null(binderResult.ValidationNode);
|
||||
Assert.Null(binderResult.Model);
|
||||
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
|
||||
}
|
||||
|
|
@ -92,6 +100,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Assert
|
||||
Assert.NotNull(binderResult);
|
||||
Assert.False(binderResult.IsModelSet);
|
||||
Assert.Null(binderResult.ValidationNode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -159,6 +168,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Returns true because it understands the metadata type.
|
||||
Assert.NotNull(binderResult);
|
||||
Assert.False(binderResult.IsModelSet);
|
||||
Assert.Null(binderResult.ValidationNode);
|
||||
Assert.Null(binderResult.Model);
|
||||
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
|
||||
var errorMessage = bindingContext.ModelState["someName"].Errors[0].Exception.Message;
|
||||
|
|
@ -192,6 +202,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.NotNull(binderResult);
|
||||
Assert.False(binderResult.IsModelSet);
|
||||
Assert.Null(binderResult.Model);
|
||||
Assert.Null(binderResult.ValidationNode);
|
||||
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
|
||||
var errorMessage = bindingContext.ModelState["someName"].Errors[0].ErrorMessage;
|
||||
Assert.Equal("Unsupported content type 'text/xyz'.", errorMessage);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var boundCollection = await binder.BindComplexCollectionFromIndexes(bindingContext, new[] { "foo", "bar", "baz" });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.ToArray());
|
||||
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.Model.ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "someName[foo]", "someName[baz]" },
|
||||
boundCollection.ValidationNode.ChildNodes.Select(o => o.Key).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -53,7 +56,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var boundCollection = await binder.BindComplexCollectionFromIndexes(bindingContext, indexNames: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { 42, 100 }, boundCollection.ToArray());
|
||||
Assert.Equal(new[] { 42, 100 }, boundCollection.Model.ToArray());
|
||||
Assert.Equal(
|
||||
new[] { "someName[0]", "someName[1]" },
|
||||
boundCollection.ValidationNode.ChildNodes.Select(o => o.Key).ToArray());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -193,8 +199,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var boundCollection = await binder.BindSimpleCollection(context, rawValue: new object[0], culture: null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(boundCollection);
|
||||
Assert.Empty(boundCollection);
|
||||
Assert.NotNull(boundCollection.Model);
|
||||
Assert.Empty(boundCollection.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -217,13 +223,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
// Arrange
|
||||
var culture = new CultureInfo("fr-FR");
|
||||
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
||||
|
||||
ModelValidationNode childValidationNode = null;
|
||||
Mock.Get<IModelBinder>(bindingContext.OperationBindingContext.ModelBinder)
|
||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns((ModelBindingContext mbc) =>
|
||||
{
|
||||
Assert.Equal("someName", mbc.ModelName);
|
||||
return Task.FromResult(new ModelBindingResult(42, mbc.ModelName, true));
|
||||
childValidationNode = new ModelValidationNode("someName", mbc.ModelMetadata, mbc.Model);
|
||||
return Task.FromResult(new ModelBindingResult(42, mbc.ModelName, true, childValidationNode));
|
||||
});
|
||||
var modelBinder = new CollectionModelBinder<int>();
|
||||
|
||||
|
|
@ -231,7 +238,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var boundCollection = await modelBinder.BindSimpleCollection(bindingContext, new int[1], culture);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { 42 }, boundCollection.ToArray());
|
||||
Assert.Equal(new[] { 42 }, boundCollection.Model.ToArray());
|
||||
Assert.Equal(new[] { childValidationNode }, boundCollection.ValidationNode.ChildNodes.ToArray());
|
||||
}
|
||||
|
||||
private static ModelBindingContext GetModelBindingContext(
|
||||
|
|
@ -267,7 +275,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
if (value != null)
|
||||
{
|
||||
var model = value.ConvertTo(mbc.ModelType);
|
||||
return new ModelBindingResult(model, mbc.ModelName, true);
|
||||
var modelValidationNode = new ModelValidationNode(mbc.ModelName, mbc.ModelMetadata, model);
|
||||
return new ModelBindingResult(model, mbc.ModelName, true, modelValidationNode);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -374,6 +374,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var model = Assert.IsType<SimplePropertiesModel>(result.Model);
|
||||
Assert.Equal("firstName-value", model.FirstName);
|
||||
Assert.Equal("lastName-value", model.LastName);
|
||||
|
||||
Assert.NotNull(result.ValidationNode);
|
||||
Assert.Equal(2, result.ValidationNode.ChildNodes.Count);
|
||||
Assert.Equal("", result.ValidationNode.Key);
|
||||
Assert.Equal(bindingContext.ModelMetadata, result.ValidationNode.ModelMetadata);
|
||||
model = Assert.IsType<SimplePropertiesModel>(result.ValidationNode.Model);
|
||||
Assert.Equal("firstName-value", model.FirstName);
|
||||
Assert.Equal("lastName-value", model.LastName);
|
||||
|
||||
Assert.Equal(2, result.ValidationNode.ChildNodes.Count);
|
||||
|
||||
var validationNode = result.ValidationNode.ChildNodes[0];
|
||||
Assert.Equal("FirstName", validationNode.Key);
|
||||
Assert.Equal("firstName-value", validationNode.Model);
|
||||
Assert.Empty(validationNode.ChildNodes);
|
||||
|
||||
validationNode = result.ValidationNode.ChildNodes[1];
|
||||
Assert.Equal("LastName", validationNode.Key);
|
||||
Assert.Equal("lastName-value", validationNode.Model);
|
||||
Assert.Empty(validationNode.ChildNodes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -414,6 +434,79 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.Equal(new byte[] { 227, 233, 133, 121, 58, 119, 180, 241 }, model.Resume);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_DoesNotAddAValidationNode_IfModelIsNotSet()
|
||||
{
|
||||
// Arrange
|
||||
var valueProvider = new SimpleHttpValueProvider();
|
||||
var mockBinder = new Mock<IModelBinder>();
|
||||
mockBinder
|
||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns(
|
||||
delegate (ModelBindingContext context)
|
||||
{
|
||||
return Task.FromResult(
|
||||
new ModelBindingResult(model: 42, key: "someName", isModelSet: false));
|
||||
});
|
||||
var binder = CreateCompositeBinder(mockBinder.Object);
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
// The result is null because of issue #2473
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_DoesNotAddAValidationNode_IfModelBindingResultIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var mockBinder = new Mock<IModelBinder>();
|
||||
mockBinder
|
||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns(Task.FromResult<ModelBindingResult>(null));
|
||||
var binder = CreateCompositeBinder(mockBinder.Object);
|
||||
var valueProvider = new SimpleHttpValueProvider();
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindModel_UsesTheValidationNodeOnModelBindingResult_IfPresent()
|
||||
{
|
||||
// Arrange
|
||||
var valueProvider = new SimpleHttpValueProvider();
|
||||
ModelValidationNode validationNode = null;
|
||||
|
||||
var mockBinder = new Mock<IModelBinder>();
|
||||
mockBinder
|
||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns(
|
||||
delegate (ModelBindingContext context)
|
||||
{
|
||||
validationNode = new ModelValidationNode("someName", context.ModelMetadata, 42);
|
||||
return Task.FromResult(
|
||||
new ModelBindingResult(42, "someName", isModelSet: true, validationNode: validationNode));
|
||||
});
|
||||
var binder = CreateCompositeBinder(mockBinder.Object);
|
||||
var bindingContext = CreateBindingContext(binder, valueProvider, typeof(SimplePropertiesModel));
|
||||
|
||||
// Act
|
||||
var result = await binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsModelSet);
|
||||
Assert.Same(validationNode, result.ValidationNode);
|
||||
}
|
||||
|
||||
private static ModelBindingContext CreateBindingContext(IModelBinder binder,
|
||||
IValueProvider valueProvider,
|
||||
Type type,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.NotNull(result);
|
||||
Assert.Null(result.Model);
|
||||
Assert.False(bindingContext.ModelState.IsValid);
|
||||
Assert.Null(result.ValidationNode);
|
||||
Assert.Equal("someName", bindingContext.ModelName);
|
||||
var error = Assert.Single(bindingContext.ModelState["someName.Key"].Errors);
|
||||
Assert.Equal("A value is required.", error.ErrorMessage);
|
||||
|
|
@ -53,6 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
Assert.NotNull(result);
|
||||
Assert.Null(result.Model);
|
||||
Assert.False(bindingContext.ModelState.IsValid);
|
||||
Assert.Null(result.ValidationNode);
|
||||
Assert.Equal("someName", bindingContext.ModelName);
|
||||
Assert.Equal(bindingContext.ModelState["someName.Value"].Errors.First().ErrorMessage, "A value is required.");
|
||||
}
|
||||
|
|
@ -97,17 +99,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(new KeyValuePair<int, string>(42, "some-value"), result.Model);
|
||||
Assert.NotNull(result.ValidationNode);
|
||||
Assert.Equal(new KeyValuePair<int, string>(42, "some-value"), result.ValidationNode.Model);
|
||||
Assert.Equal("someName", result.ValidationNode.Key);
|
||||
|
||||
var validationNode = result.ValidationNode.ChildNodes[0];
|
||||
Assert.Equal("someName.Key", validationNode.Key);
|
||||
Assert.Equal(42, validationNode.Model);
|
||||
Assert.Empty(validationNode.ChildNodes);
|
||||
|
||||
validationNode = result.ValidationNode.ChildNodes[1];
|
||||
Assert.Equal("someName.Value", validationNode.Key);
|
||||
Assert.Equal("some-value", validationNode.Model);
|
||||
Assert.Empty(validationNode.ChildNodes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryBindStrongModel_BinderExists_BinderReturnsCorrectlyTypedObject_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
ModelBindingContext bindingContext = GetBindingContext(new SimpleHttpValueProvider());
|
||||
var bindingContext = GetBindingContext(new SimpleHttpValueProvider());
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
var modelValidationNodeList = new List<ModelValidationNode>();
|
||||
|
||||
// Act
|
||||
var result = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
||||
var result = await binder.TryBindStrongModel<int>(bindingContext, "key", modelValidationNodeList);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsModelSet);
|
||||
|
|
@ -131,9 +147,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
|
||||
|
||||
var binder = new KeyValuePairModelBinder<int, string>();
|
||||
var modelValidationNodeList = new List<ModelValidationNode>();
|
||||
|
||||
// Act
|
||||
var result = await binder.TryBindStrongModel<int>(bindingContext, "key");
|
||||
var result = await binder.TryBindStrongModel<int>(bindingContext, "key", modelValidationNodeList);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsModelSet);
|
||||
|
|
|
|||
|
|
@ -776,11 +776,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
"John Doe",
|
||||
isModelSet: true,
|
||||
key: "");
|
||||
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelStateDictionary = bindingContext.ModelState;
|
||||
|
|
@ -827,10 +827,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
isModelSet: true,
|
||||
key: "");
|
||||
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelStateDictionary = bindingContext.ModelState;
|
||||
|
|
@ -888,8 +889,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
isModelSet: true,
|
||||
key: "theModel.Age");
|
||||
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelStateDictionary = bindingContext.ModelState;
|
||||
|
|
@ -920,9 +923,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Set no properties though Age (a non-Nullable struct) and City (a class) properties are required.
|
||||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelStateDictionary = bindingContext.ModelState;
|
||||
|
|
@ -973,9 +977,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
null,
|
||||
isModelSet: true,
|
||||
key: "theModel.City");
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelStateDictionary = bindingContext.ModelState;
|
||||
|
|
@ -1004,9 +1009,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Set no properties though ValueTypeRequired (a non-Nullable struct) property is required.
|
||||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelStateDictionary = bindingContext.ModelState;
|
||||
|
|
@ -1074,9 +1080,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
model: null,
|
||||
isModelSet: isModelSet,
|
||||
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.False(modelStateDictionary.IsValid);
|
||||
|
|
@ -1149,9 +1156,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
model: null,
|
||||
isModelSet: false,
|
||||
key: "theModel." + nameof(Person.PropertyWithDefaultValue));
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelStateDictionary.IsValid);
|
||||
|
|
@ -1191,17 +1199,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
|
||||
dto.Results[dobProperty] = null;
|
||||
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
testableBinder.ProcessDto(bindingContext, dto, modelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("John", model.FirstName);
|
||||
Assert.Equal("Doe", model.LastName);
|
||||
Assert.Equal(dob, model.DateOfBirth);
|
||||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
|
||||
// Ensure that we add child nodes for all the nodes which have a result (irrespective of if they
|
||||
// are bound or not).
|
||||
Assert.Equal(2, modelValidationNode.ChildNodes.Count());
|
||||
|
||||
var validationNode = modelValidationNode.ChildNodes[0];
|
||||
Assert.Equal("", validationNode.Key);
|
||||
Assert.Equal("John", validationNode.Model);
|
||||
|
||||
validationNode = modelValidationNode.ChildNodes[1];
|
||||
Assert.Equal("", validationNode.Key);
|
||||
Assert.Equal("Doe", validationNode.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -231,7 +231,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
private static ModelValidationContext CreateValidationContext(ModelExplorer modelExplorer)
|
||||
{
|
||||
return new ModelValidationContext(
|
||||
rootPrefix: null,
|
||||
bindingSource: null,
|
||||
modelState: null,
|
||||
validatorProvider: null,
|
||||
|
|
|
|||
|
|
@ -212,9 +212,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var context = GetModelValidationContext(model, type);
|
||||
|
||||
var validator = new DefaultObjectValidator(context.ExcludeFilters, context.ModelMetadataProvider);
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(string.Empty, context.ModelValidationContext.ModelExplorer.Metadata, model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act
|
||||
validator.Validate(context.ModelValidationContext);
|
||||
validator.Validate(context.ModelValidationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
var actualErrors = new Dictionary<string, string>();
|
||||
|
|
@ -240,6 +245,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
{
|
||||
// Arrange
|
||||
var testValidationContext = GetModelValidationContext(new Uri("/api/values", UriKind.Relative), typeof(Uri));
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
string.Empty,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws(
|
||||
|
|
@ -249,7 +262,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider)
|
||||
.Validate(testValidationContext.ModelValidationContext);
|
||||
.Validate(testValidationContext.ModelValidationContext, topLevelValidationNode);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +278,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
yield return new object[] { new Dictionary<string, Uri> {
|
||||
{ "values", new Uri("/api/values", UriKind.Relative) },
|
||||
{ "hello", new Uri("/api/hello", UriKind.Relative) }
|
||||
}, typeof(Uri), new List<Type>() { typeof(Uri) } };
|
||||
}, typeof(Dictionary<string, Uri>), new List<Type>() { typeof(Uri) } };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,13 +289,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
object input, Type type, List<Type> excludedTypes)
|
||||
{
|
||||
// Arrange
|
||||
var testValidationContext = GetModelValidationContext(input, type, string.Empty, excludedTypes);
|
||||
var testValidationContext = GetModelValidationContext(input, type, excludedTypes);
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
string.Empty,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act & Assert (does not throw)
|
||||
new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider)
|
||||
.Validate(testValidationContext.ModelValidationContext);
|
||||
.Validate(testValidationContext.ModelValidationContext, topLevelValidationNode);
|
||||
Assert.True(testValidationContext.ModelValidationContext.ModelState.IsValid);
|
||||
}
|
||||
|
||||
|
|
@ -294,6 +315,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var testValidationContext = GetModelValidationContext(
|
||||
new Uri("/api/values", UriKind.Relative), typeof(Uri));
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
string.Empty,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
|
|
@ -302,7 +331,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider)
|
||||
.Validate(validationContext);
|
||||
.Validate(validationContext, topLevelValidationNode);
|
||||
});
|
||||
Assert.True(validationContext.ModelState.IsValid);
|
||||
}
|
||||
|
|
@ -315,12 +344,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var model = new Address() { Street = "Microsoft Way" };
|
||||
var testValidationContext = GetModelValidationContext(model, model.GetType());
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
string.Empty,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act (does not throw)
|
||||
new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider)
|
||||
.Validate(validationContext);
|
||||
.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Street", validationContext.ModelState.Keys);
|
||||
|
|
@ -340,12 +377,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
new TypeThatOverridesEquals { Funny = "hehe" }
|
||||
};
|
||||
var testValidationContext = GetModelValidationContext(instance, typeof(TypeThatOverridesEquals[]));
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
string.Empty,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act & Assert (does not throw)
|
||||
new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider)
|
||||
.Validate(testValidationContext.ModelValidationContext);
|
||||
.Validate(testValidationContext.ModelValidationContext, topLevelValidationNode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -361,7 +406,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var testValidationContext = GetModelValidationContext(
|
||||
user,
|
||||
typeof(User),
|
||||
"user",
|
||||
new List<Type> { typeof(string) });
|
||||
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
|
|
@ -370,9 +414,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
"user",
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext);
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { "key1", "user.Password", "", "user.ConfirmPassword" },
|
||||
|
|
@ -380,7 +432,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var modelState = validationContext.ModelState["user.ConfirmPassword"];
|
||||
Assert.Empty(modelState.Errors);
|
||||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||
|
||||
|
||||
var error = Assert.Single(validationContext.ModelState[""].Errors);
|
||||
Assert.IsType<TooManyModelErrorsException>(error.Exception);
|
||||
}
|
||||
|
|
@ -398,15 +450,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var testValidationContext = GetModelValidationContext(
|
||||
user,
|
||||
typeof(User),
|
||||
"user",
|
||||
new List<Type> { typeof(User) });
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
"user",
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext);
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.False(validationContext.ModelState.ContainsKey("user.Password"));
|
||||
|
|
@ -429,7 +488,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var testValidationContext = GetModelValidationContext(
|
||||
user,
|
||||
typeof(User),
|
||||
"user",
|
||||
new List<Type> { typeof(User) });
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
|
||||
|
|
@ -440,9 +498,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
"user",
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext);
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelState = validationContext.ModelState["user.Password"];
|
||||
|
|
@ -455,21 +521,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void NonRequestBoundModel_MarkedAsSkipped()
|
||||
public void Validate_IfSuppressIsSet_MarkedAsSkipped()
|
||||
{
|
||||
// Arrange
|
||||
var testValidationContext = GetModelValidationContext(
|
||||
new TestServiceProvider(),
|
||||
typeof(TestServiceProvider),
|
||||
"serviceProvider");
|
||||
typeof(TestServiceProvider));
|
||||
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var modelExplorer = testValidationContext.ModelValidationContext.ModelExplorer;
|
||||
var topLevelValidationNode = new ModelValidationNode(
|
||||
"serviceProvider",
|
||||
modelExplorer.Metadata,
|
||||
modelExplorer.Model);
|
||||
|
||||
var propertyExplorer = modelExplorer.GetExplorerForProperty("TestService");
|
||||
var childNode = new ModelValidationNode(
|
||||
"serviceProvider.TestService",
|
||||
propertyExplorer.Metadata,
|
||||
propertyExplorer.Model)
|
||||
{
|
||||
SuppressValidation = true
|
||||
};
|
||||
|
||||
topLevelValidationNode.ChildNodes.Add(childNode);
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext);
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.True(validationContext.ModelState.IsValid);
|
||||
|
|
@ -496,7 +577,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var testValidationContext = GetModelValidationContext(
|
||||
model,
|
||||
type,
|
||||
"items",
|
||||
excludedTypes: null,
|
||||
modelStateDictionary: modelStateDictionary);
|
||||
|
||||
|
|
@ -509,9 +589,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
"items",
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext);
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.True(validationContext.ModelState.IsValid);
|
||||
|
|
@ -538,7 +626,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
var testValidationContext = GetModelValidationContext(
|
||||
model,
|
||||
typeof(Dictionary<string, string>),
|
||||
"items",
|
||||
excludedTypes: null,
|
||||
modelStateDictionary: modelStateDictionary);
|
||||
|
||||
|
|
@ -552,8 +639,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
|
||||
var topLevelValidationNode =
|
||||
new ModelValidationNode(
|
||||
"items",
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Metadata,
|
||||
testValidationContext.ModelValidationContext.ModelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext);
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.True(validationContext.ModelState.IsValid);
|
||||
|
|
@ -569,19 +665,88 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
Assert.Equal(modelState.ValidationState, ModelValidationState.Skipped);
|
||||
}
|
||||
|
||||
private TestModelValidationContext GetModelValidationContext(
|
||||
object model,
|
||||
Type type,
|
||||
string key = "",
|
||||
List<Type> excludedTypes = null)
|
||||
[Fact]
|
||||
public void Validator_IfValidateAllPropertiesIsNotSet_DoesNotAutoExpand()
|
||||
{
|
||||
return GetModelValidationContext(model, type, key, excludedTypes, new ModelStateDictionary());
|
||||
// Arrange
|
||||
var testValidationContext = GetModelValidationContext(
|
||||
LonelyPerson,
|
||||
typeof(Person));
|
||||
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var modelExplorer = testValidationContext.ModelValidationContext.ModelExplorer;
|
||||
|
||||
// No ChildNode added
|
||||
var topLevelValidationNode = new ModelValidationNode(
|
||||
"person",
|
||||
modelExplorer.Metadata,
|
||||
modelExplorer.Model);
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
Assert.True(validationContext.ModelState.IsValid);
|
||||
var key = Assert.Single(validationContext.ModelState.Keys);
|
||||
Assert.Equal("person", key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validator_IfValidateAllPropertiesSet_WithChildNodes_DoesNotAutoExpand()
|
||||
{
|
||||
// Arrange
|
||||
var testValidationContext = GetModelValidationContext(
|
||||
LonelyPerson,
|
||||
typeof(Person));
|
||||
|
||||
var validationContext = testValidationContext.ModelValidationContext;
|
||||
var validator = new DefaultObjectValidator(
|
||||
testValidationContext.ExcludeFilters,
|
||||
testValidationContext.ModelMetadataProvider);
|
||||
var modelExplorer = testValidationContext.ModelValidationContext.ModelExplorer;
|
||||
|
||||
var topLevelValidationNode = new ModelValidationNode(
|
||||
"person",
|
||||
modelExplorer.Metadata,
|
||||
modelExplorer.Model)
|
||||
{
|
||||
ValidateAllProperties = true
|
||||
};
|
||||
|
||||
var propertyExplorer = modelExplorer.GetExplorerForProperty("Profession");
|
||||
var childNode = new ModelValidationNode(
|
||||
"person.Profession",
|
||||
propertyExplorer.Metadata,
|
||||
propertyExplorer.Model);
|
||||
|
||||
topLevelValidationNode.ChildNodes.Add(childNode);
|
||||
|
||||
// Act
|
||||
validator.Validate(validationContext, topLevelValidationNode);
|
||||
|
||||
// Assert
|
||||
var modelState = validationContext.ModelState;
|
||||
Assert.False(modelState.IsValid);
|
||||
|
||||
// Since the model is invalid at property level there is no entry in the model state for top level node.
|
||||
Assert.Single(modelState.Keys, k => k == "person.Profession");
|
||||
Assert.Equal(1, modelState.Count);
|
||||
}
|
||||
|
||||
private TestModelValidationContext GetModelValidationContext(
|
||||
object model,
|
||||
Type type,
|
||||
List<Type> excludedTypes = null)
|
||||
{
|
||||
return GetModelValidationContext(model, type, excludedTypes, new ModelStateDictionary());
|
||||
}
|
||||
|
||||
private TestModelValidationContext GetModelValidationContext(
|
||||
object model,
|
||||
Type type,
|
||||
string key,
|
||||
List<Type> excludedTypes,
|
||||
ModelStateDictionary modelStateDictionary)
|
||||
{
|
||||
|
|
@ -603,7 +768,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
|||
return new TestModelValidationContext
|
||||
{
|
||||
ModelValidationContext = new ModelValidationContext(
|
||||
key,
|
||||
null,
|
||||
TestModelValidatorProvider.CreateDefaultProvider(),
|
||||
modelStateDictionary,
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidatorProvider
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
|
||||
.Verifiable();
|
||||
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
// Assert
|
||||
mockValidatorProvider.Verify(
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Once());
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -197,8 +197,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
};
|
||||
|
||||
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidatorProvider.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||
.Verifiable();
|
||||
mockValidatorProvider
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
|
||||
.Verifiable();
|
||||
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
|
||||
|
||||
// Act
|
||||
|
|
@ -206,7 +207,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
.BindActionArgumentsAsync(actionContext, actionBindingContext, new TestController());
|
||||
|
||||
// Assert
|
||||
mockValidatorProvider.Verify(o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Never());
|
||||
mockValidatorProvider.Verify(
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -226,7 +229,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidatorProvider
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
|
||||
.Verifiable();
|
||||
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
|
||||
|
||||
|
|
@ -236,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
|
||||
// Assert
|
||||
mockValidatorProvider.Verify(
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Once());
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -268,8 +271,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
};
|
||||
|
||||
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidatorProvider.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()))
|
||||
.Verifiable();
|
||||
mockValidatorProvider
|
||||
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
|
||||
.Verifiable();
|
||||
|
||||
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
|
||||
|
||||
// Act
|
||||
|
|
@ -277,7 +282,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
.BindActionArgumentsAsync(actionContext, actionBindingContext, new TestController());
|
||||
|
||||
// Assert
|
||||
mockValidatorProvider.Verify(o => o.Validate(It.IsAny<ModelValidationContext>()), Times.Never());
|
||||
mockValidatorProvider.Verify(
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -596,7 +603,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
if (validator == null)
|
||||
{
|
||||
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
|
||||
mockValidator.Setup(o => o.Validate(It.IsAny<ModelValidationContext>()));
|
||||
mockValidator.Setup(
|
||||
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()));
|
||||
validator = mockValidator.Object;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -281,7 +281,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
{
|
||||
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
return Task.FromResult(new ModelBindingResult("Success", bindingContext.ModelName, true));
|
||||
var model = "Success";
|
||||
var modelValidationNode = new ModelValidationNode(
|
||||
bindingContext.ModelName,
|
||||
bindingContext.ModelMetadata,
|
||||
model);
|
||||
return Task.FromResult(new ModelBindingResult(model, bindingContext.ModelName, true, modelValidationNode));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -536,5 +539,63 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
private class Person4
|
||||
{
|
||||
public IList<Address4> Addresses { get; set; }
|
||||
}
|
||||
|
||||
private class Address4
|
||||
{
|
||||
public int Zip { get; set; }
|
||||
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
[Fact(Skip = "Extra ModelState key because of #2446")]
|
||||
public async Task CollectionModelBinder_UsesCustomIndexes()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(Person4)
|
||||
};
|
||||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
var formCollection = new FormCollection(new Dictionary<string, string[]>()
|
||||
{
|
||||
{ "Addresses.index", new [] { "Key1", "Key2" } },
|
||||
{ "Addresses[Key1].Street", new [] { "Street1" } },
|
||||
{ "Addresses[Key2].Street", new [] { "Street2" } },
|
||||
});
|
||||
|
||||
request.Form = formCollection;
|
||||
request.ContentType = "application/x-www-form-urlencoded";
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.IsType<Person4>(modelBindingResult.Model);
|
||||
|
||||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key1].Street").Value;
|
||||
Assert.Equal("Street1", entry.Value.AttemptedValue);
|
||||
Assert.Equal("Street1", entry.Value.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key2].Street").Value;
|
||||
Assert.Equal("Street2", entry.Value.AttemptedValue);
|
||||
Assert.Equal("Street2", entry.Value.RawValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue