Rewrite of validation

This commit is contained in:
Ryan Nowak 2015-09-02 15:59:55 -07:00
parent 0889b18f95
commit 8a502dbe5d
64 changed files with 2934 additions and 1698 deletions

View File

@ -3,6 +3,7 @@
using System;
using Microsoft.Framework.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@ -59,6 +60,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelState = modelState,
OperationBindingContext = operationBindingContext,
ValueProvider = operationBindingContext.ValueProvider,
ValidationState = new ValidationStateDictionary(),
};
}
@ -74,6 +77,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelState = parent.ModelState,
OperationBindingContext = parent.OperationBindingContext,
ValueProvider = parent.ValueProvider,
ValidationState = parent.ValidationState,
Model = model,
ModelMetadata = modelMetadata,
@ -174,5 +178,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// is eligible for model binding.
/// </summary>
public Func<ModelBindingContext, string, bool> PropertyFilter { get; set; }
/// <summary>
/// Gets or sets the <see cref="ValidationStateDictionary"/>. Used for tracking validation state to
/// customize validation behavior for a model object.
/// </summary>
public ValidationStateDictionary ValidationState { get; set; }
}
}

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <returns>A <see cref="ModelBindingResult"/> representing a failed model binding operation.</returns>
public static ModelBindingResult Failed([NotNull] string key)
{
return new ModelBindingResult(key, model: null, isModelSet: false, validationNode: null);
return new ModelBindingResult(key, model: null, isModelSet: false);
}
/// <summary>
@ -49,14 +49,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
/// <param name="key">The key of the current model binding operation.</param>
/// <param name="model">The model value. May be <c>null.</c></param>
/// <param name="validationNode">The <see cref="ModelValidationNode"/>. May be <c>null</c>.</param>
/// <returns>A <see cref="ModelBindingResult"/> representing a successful model bind.</returns>
public static ModelBindingResult Success(
[NotNull] string key,
object model,
ModelValidationNode validationNode)
object model)
{
return new ModelBindingResult(key, model, isModelSet: true, validationNode: validationNode);
return new ModelBindingResult(key, model, isModelSet: true);
}
/// <summary>
@ -65,22 +63,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
/// <param name="key">The key of the current model binding operation.</param>
/// <param name="model">The model value. May be <c>null.</c></param>
/// <param name="validationNode">The <see cref="ModelValidationNode"/>. May be <c>null</c>.</param>
/// <returns>A completed <see cref="Task{ModelBindingResult}"/> representing a successful model bind.</returns>
public static Task<ModelBindingResult> SuccessAsync(
[NotNull] string key,
object model,
ModelValidationNode validationNode)
object model)
{
return Task.FromResult(Success(key, model, validationNode));
return Task.FromResult(Success(key, model));
}
private ModelBindingResult(string key, object model, bool isModelSet, ModelValidationNode validationNode)
private ModelBindingResult(string key, object model, bool isModelSet)
{
Key = key;
Model = model;
IsModelSet = isModelSet;
ValidationNode = validationNode;
}
/// <summary>
@ -109,11 +104,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public bool IsModelSet { get; }
/// <summary>
/// A <see cref="ModelValidationNode"/> associated with the current <see cref="ModelBindingResult"/>.
/// </summary>
public ModelValidationNode ValidationNode { get; }
/// <inheritdoc />
public override bool Equals(object obj)
{

View File

@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.Internal;
@ -14,6 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// A metadata representation of a model type, property or parameter.
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
public abstract class ModelMetadata
{
private bool? _isComplexType;
@ -407,5 +409,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
return DisplayName ?? PropertyName ?? ModelType.Name;
}
private string DebuggerToString()
{
if (Identity.MetadataKind == ModelMetadataKind.Type)
{
return $"ModelMetadata (Type: '{ModelType.Name}')";
}
else
{
return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')";
}
}
}
}

View File

@ -1,76 +0,0 @@
// 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; }
}
}

View File

@ -0,0 +1,23 @@
// 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;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Defines a strategy for enumerating the child entries of a model object which should be validated.
/// </summary>
public interface IValidationStrategy
{
/// <summary>
/// Gets an <see cref="IEnumerator{ValidationEntry}"/> containing a <see cref="ValidationEntry"/> for
/// each child entry of the model object to be validated.
/// </summary>
/// <param name="metadata">The <see cref="ModelMetadata"/> associated with <paramref name="model"/>.</param>
/// <param name="key">The model prefix associated with <paramref name="model"/>.</param>
/// <param name="model">The model object.</param>
/// <returns>An <see cref="IEnumerator{ValidationEntry}"/>.</returns>
IEnumerator<ValidationEntry> GetChildren(ModelMetadata metadata, string key, object model);
}
}

View File

@ -1,64 +1,26 @@
// 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 Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// A context object for <see cref="IModelValidator"/>.
/// </summary>
public class ModelValidationContext
{
public ModelValidationContext(
[NotNull] ModelBindingContext bindingContext,
[NotNull] ModelExplorer modelExplorer)
: this(
bindingContext.BindingSource,
bindingContext.OperationBindingContext.ValidatorProvider,
bindingContext.ModelState,
modelExplorer)
{
}
public ModelValidationContext(
BindingSource bindingSource,
[NotNull] IModelValidatorProvider validatorProvider,
[NotNull] ModelStateDictionary modelState,
[NotNull] ModelExplorer modelExplorer)
{
ModelState = modelState;
ValidatorProvider = validatorProvider;
ModelExplorer = modelExplorer;
BindingSource = bindingSource;
}
/// <summary>
/// Gets or sets the model object.
/// </summary>
public object Model { get; set; }
/// <summary>
/// Constructs a new instance of the <see cref="ModelValidationContext"/> class using the
/// <paramref name="parentContext" /> and <paramref name="modelExplorer"/>.
/// Gets or sets the model container object.
/// </summary>
/// <param name="parentContext">Existing <see cref="ModelValidationContext"/>.</param>
/// <param name="modelExplorer">
/// <see cref="ModelExplorer"/> associated with the new <see cref="ModelValidationContext"/>.
/// </param>
/// <returns>
/// A new instance of the <see cref="ModelValidationContext"/> class using the
/// <paramref name="parentContext" /> and <paramref name="modelExplorer"/>.
/// </returns>
public static ModelValidationContext GetChildValidationContext(
[NotNull] ModelValidationContext parentContext,
[NotNull] ModelExplorer modelExplorer)
{
return new ModelValidationContext(
modelExplorer.Metadata.BindingSource,
parentContext.ValidatorProvider,
parentContext.ModelState,
modelExplorer);
}
public object Container { get; set; }
public ModelExplorer ModelExplorer { get; }
public ModelStateDictionary ModelState { get; }
public BindingSource BindingSource { get; set; }
public IModelValidatorProvider ValidatorProvider { get; }
/// <summary>
/// Gets or sets the <see cref="ModelMetadata"/> associated with <see cref="Model"/>.
/// </summary>
public ModelMetadata Metadata { get; set; }
}
}

View File

@ -0,0 +1,51 @@
// 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;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Contains data needed for validating a child entry of a model object. See <see cref="IValidationStrategy"/>.
/// </summary>
public struct ValidationEntry
{
/// <summary>
/// Creates a new <see cref="ValidationEntry"/>.
/// </summary>
/// <param name="metadata">The <see cref="ModelMetadata"/> associated with <paramref name="model"/>.</param>
/// <param name="key">The model prefix associated with <paramref name="model"/>.</param>
/// <param name="model">The model object.</param>
public ValidationEntry(ModelMetadata metadata, string key, object model)
{
if (metadata == null)
{
throw new ArgumentNullException(nameof(metadata));
}
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
Metadata = metadata;
Key = key;
Model = model;
}
/// <summary>
/// The model prefix associated with <see cref="Model"/>.
/// </summary>
public string Key { get; }
/// <summary>
/// The <see cref="ModelMetadata"/> associated with <see cref="Model"/>.
/// </summary>
public ModelMetadata Metadata { get; }
/// <summary>
/// The model object.
/// </summary>
public object Model { get; }
}
}

View File

@ -0,0 +1,179 @@
// 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;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Used for tracking validation state to customize validation behavior for a model object.
/// </summary>
public class ValidationStateDictionary :
IDictionary<object, ValidationStateEntry>,
IReadOnlyDictionary<object, ValidationStateEntry>
{
private readonly Dictionary<object, ValidationStateEntry> _inner;
/// <summary>
/// Creates a new <see cref="ValidationStateDictionary"/>.
/// </summary>
public ValidationStateDictionary()
{
_inner = new Dictionary<object, ValidationStateEntry>(ReferenceEqualityComparer.Instance);
}
/// <inheritdoc />
public ValidationStateEntry this[object key]
{
get
{
ValidationStateEntry entry;
TryGetValue(key, out entry);
return entry;
}
set
{
_inner[key] = value;
}
}
/// <inheritdoc />
public int Count
{
get
{
return _inner.Count;
}
}
/// <inheritdoc />
public bool IsReadOnly
{
get
{
return ((IDictionary<object, ValidationStateEntry>)_inner).IsReadOnly;
}
}
/// <inheritdoc />
public ICollection<object> Keys
{
get
{
return ((IDictionary<object, ValidationStateEntry>)_inner).Keys;
}
}
/// <inheritdoc />
public ICollection<ValidationStateEntry> Values
{
get
{
return ((IDictionary<object, ValidationStateEntry>)_inner).Values;
}
}
/// <inheritdoc />
IEnumerable<object> IReadOnlyDictionary<object, ValidationStateEntry>.Keys
{
get
{
return ((IReadOnlyDictionary<object, ValidationStateEntry>)_inner).Keys;
}
}
/// <inheritdoc />
IEnumerable<ValidationStateEntry> IReadOnlyDictionary<object, ValidationStateEntry>.Values
{
get
{
return ((IReadOnlyDictionary<object, ValidationStateEntry>)_inner).Values;
}
}
/// <inheritdoc />
public void Add(KeyValuePair<object, ValidationStateEntry> item)
{
((IDictionary<object, ValidationStateEntry>)_inner).Add(item);
}
/// <inheritdoc />
public void Add(object key, ValidationStateEntry value)
{
_inner.Add(key, value);
}
/// <inheritdoc />
public void Clear()
{
_inner.Clear();
}
/// <inheritdoc />
public bool Contains(KeyValuePair<object, ValidationStateEntry> item)
{
return ((IDictionary<object, ValidationStateEntry>)_inner).Contains(item);
}
/// <inheritdoc />
public bool ContainsKey(object key)
{
return _inner.ContainsKey(key);
}
/// <inheritdoc />
public void CopyTo(KeyValuePair<object, ValidationStateEntry>[] array, int arrayIndex)
{
((IDictionary<object, ValidationStateEntry>)_inner).CopyTo(array, arrayIndex);
}
/// <inheritdoc />
public IEnumerator<KeyValuePair<object, ValidationStateEntry>> GetEnumerator()
{
return ((IDictionary<object, ValidationStateEntry>)_inner).GetEnumerator();
}
/// <inheritdoc />
public bool Remove(KeyValuePair<object, ValidationStateEntry> item)
{
return _inner.Remove(item);
}
/// <inheritdoc />
public bool Remove(object key)
{
return _inner.Remove(key);
}
/// <inheritdoc />
public bool TryGetValue(object key, out ValidationStateEntry value)
{
return _inner.TryGetValue(key, out value);
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<object, ValidationStateEntry>)_inner).GetEnumerator();
}
private class ReferenceEqualityComparer : IEqualityComparer<object>
{
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
public new bool Equals(object x, object y)
{
return Object.ReferenceEquals(x, y);
}
public int GetHashCode(object obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
}
}

View File

@ -0,0 +1,33 @@
// 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 Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// An entry in a <see cref="ValidationStateDictionary"/>. Records state information to override the default
/// behavior of validation for an object.
/// </summary>
public class ValidationStateEntry
{
/// <summary>
/// Gets or sets the model prefix associated with the entry.
/// </summary>
public string Key { get; set; }
/// <summary>
/// Gets or sets the <see cref="ModelMetadata"/> associated with the entry.
/// </summary>
public ModelMetadata Metadata { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the associated model object should be validated.
/// </summary>
public bool SuppressValidation { get; set; }
/// <summary>
/// Gets or sets an <see cref="IValidationStrategy"/> for enumerating child entries of the associated
/// model object.
/// </summary>
public IValidationStrategy Strategy { get; set; }
}
}

View File

@ -82,20 +82,14 @@ namespace Microsoft.AspNet.Mvc.Controllers
parameter.Name);
var modelBindingResult = await operationContext.ModelBinder.BindModelAsync(modelBindingContext);
if (modelBindingResult.IsModelSet &&
modelBindingResult.ValidationNode != null)
if (modelBindingResult.IsModelSet)
{
var modelExplorer = new ModelExplorer(
_modelMetadataProvider,
metadata,
modelBindingResult.Model);
var validationContext = new ModelValidationContext(
modelBindingContext.BindingSource,
_validator.Validate(
operationContext.ValidatorProvider,
modelState,
modelExplorer);
_validator.Validate(validationContext, modelBindingResult.ValidationNode);
modelBindingContext.ValidationState,
modelBindingResult.Key,
modelBindingResult.Model);
}
return modelBindingResult;

View File

@ -87,12 +87,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return ModelBindingResult.Failed(modelBindingKey);
}
var validationNode = new ModelValidationNode(modelBindingKey, bindingContext.ModelMetadata, model)
{
ValidateAllProperties = true
};
return ModelBindingResult.Success(modelBindingKey, model, validationNode);
return ModelBindingResult.Success(modelBindingKey, model);
}
catch (Exception ex)
{

View File

@ -44,12 +44,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
try
{
var model = Convert.FromBase64String(value);
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
catch (Exception ex)
{

View File

@ -17,13 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (bindingContext.ModelType == typeof(CancellationToken))
{
var model = bindingContext.OperationBindingContext.HttpContext.RequestAborted;
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model)
{
SuppressValidation = true,
};
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
return ModelBindingResult.NoResultAsync;

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Reflection;
#endif
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -38,12 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
model = CreateEmptyCollection(bindingContext.ModelType);
}
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.Success(bindingContext.ModelName, model);
}
return ModelBindingResult.NoResult;
@ -72,6 +68,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
CopyToModel(model, boundCollection);
}
Debug.Assert(model != null);
if (result.ValidationStrategy != null)
{
bindingContext.ValidationState.Add(model, new ValidationStateEntry()
{
Strategy = result.ValidationStrategy,
});
}
if (valueProviderResult != ValueProviderResult.None)
{
// If we did simple binding, then modelstate should be updated to reflect what we bound for ModelName.
@ -81,7 +86,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
valueProviderResult);
}
return ModelBindingResult.Success(bindingContext.ModelName, model, result.ValidationNode);
return ModelBindingResult.Success(bindingContext.ModelName, model);
}
/// <inheritdoc />
@ -137,11 +142,6 @@ 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 innerBindingContext = ModelBindingContext.CreateChildBindingContext(
bindingContext,
elementMetadata,
@ -164,18 +164,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (result != null && result.IsModelSet)
{
boundValue = result.Model;
if (result.ValidationNode != null)
{
validationNode.ChildNodes.Add(result.ValidationNode);
}
boundCollection.Add(ModelBindingHelper.CastOrDefault<TElement>(boundValue));
}
}
return new CollectionResult
{
ValidationNode = validationNode,
Model = boundCollection
};
}
@ -211,10 +205,7 @@ 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);
@ -235,10 +226,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
didBind = true;
boundValue = result.Model;
if (result.ValidationNode != null)
{
validationNode.ChildNodes.Add(result.ValidationNode);
}
}
// infinite size collection stops on first bind failure
@ -252,17 +239,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return new CollectionResult
{
ValidationNode = validationNode,
Model = boundCollection
Model = boundCollection,
// If we're working with a fixed set of indexes then this is the format like:
//
// ?parameter.index=zero,one,two&parameter[zero]=0&&parameter[one]=1&parameter[two]=2...
//
// We need to provide this data to the validation system so it can 'replay' the keys.
// But we can't just set ValidationState here, because it needs the 'real' model.
ValidationStrategy = indexNamesIsFinite ?
new ExplicitIndexCollectionValidationStrategy(indexNames) :
null,
};
}
// Internal for testing.
internal class CollectionResult
{
public ModelValidationNode ValidationNode { get; set; }
public IEnumerable<TElement> Model { get; set; }
public IValidationStrategy ValidationStrategy { get; set; }
}
/// <summary>

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -60,6 +61,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (result.IsModelSet ||
bindingContext.ModelState.ContainsKey(bindingContext.ModelName))
{
if (bindingContext.IsTopLevelObject && result.Model != null)
{
ValidationStateEntry entry;
if (!bindingContext.ValidationState.TryGetValue(result.Model, out entry))
{
entry = new ValidationStateEntry();
bindingContext.ValidationState.Add(result.Model, entry);
}
entry.Key = entry.Key ?? result.Key;
entry.Metadata = entry.Metadata ?? bindingContext.ModelMetadata;
}
return result;
}
@ -123,6 +137,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
BindingSource = oldBindingContext.BindingSource,
BinderType = oldBindingContext.BinderType,
IsTopLevelObject = oldBindingContext.IsTopLevelObject,
ValidationState = oldBindingContext.ValidationState,
};
if (bindingSource != null && bindingSource.IsGreedy)

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.Reflection;
#endif
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -24,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public override async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
var result = await base.BindModelAsync(bindingContext);
if (result == null || !result.IsModelSet)
if (!result.IsModelSet)
{
// No match for the prefix at all.
return result;
@ -65,8 +66,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
model: null);
var modelBinder = bindingContext.OperationBindingContext.ModelBinder;
var validationNode = result.ValidationNode;
var keyMappings = new Dictionary<string, TKey>(StringComparer.Ordinal);
foreach (var kvp in keys)
{
// Use InvariantCulture to convert the key since ExpressionHelper.GetExpressionText() would use
@ -79,12 +80,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Always add an entry to the dictionary but validate only if binding was successful.
model[convertedKey] = ModelBindingHelper.CastOrDefault<TValue>(valueResult.Model);
if (valueResult.IsModelSet)
{
validationNode.ChildNodes.Add(valueResult.ValidationNode);
}
keyMappings.Add(kvp.Key, convertedKey);
}
bindingContext.ValidationState.Add(model, new ValidationStateEntry()
{
Strategy = new ShortFormDictionaryValidationStrategy<TKey, TValue>(keyMappings, valueMetadata),
});
return result;
}

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Primitives;
@ -46,13 +47,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
model = new EmptyFormCollection();
}
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model)
{
SuppressValidation = true,
};
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
return ModelBindingResult.Success(bindingContext.ModelName, model);
}
private class EmptyFormCollection : IFormCollection

View File

@ -10,6 +10,7 @@ using System.Reflection;
#endif
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
@ -62,18 +63,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
else
{
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value)
{
SuppressValidation = true,
};
bindingContext.ValidationState.Add(value, new ValidationStateEntry() { SuppressValidation = true });
bindingContext.ModelState.SetModelValue(
bindingContext.ModelName,
rawValue: null,
attemptedValue: null);
return ModelBindingResult.Success(bindingContext.ModelName, value, validationNode);
return ModelBindingResult.Success(bindingContext.ModelName, value);
}
}

View File

@ -63,17 +63,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
else
{
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
bindingContext.ModelState.SetModelValue(
bindingContext.ModelName,
request.Headers.GetCommaSeparatedValues(headerName),
request.Headers[headerName]);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
}
}

View File

@ -15,9 +15,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
typeof(KeyValuePair<TKey, TValue>),
allowNullModel: true);
var childNodes = new List<ModelValidationNode>();
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key", childNodes);
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value", childNodes);
var keyResult = await TryBindStrongModel<TKey>(bindingContext, "Key");
var valueResult = await TryBindStrongModel<TValue>(bindingContext, "Value");
if (keyResult.IsModelSet && valueResult.IsModelSet)
{
@ -25,15 +24,7 @@ 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);
return ModelBindingResult.Success(bindingContext.ModelName, model, modelValidationNode);
return ModelBindingResult.Success(bindingContext.ModelName, model);
}
else if (!keyResult.IsModelSet && valueResult.IsModelSet)
{
@ -62,13 +53,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (bindingContext.IsTopLevelObject)
{
var model = new KeyValuePair<TKey, TValue>();
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.Success(bindingContext.ModelName, model);
}
return ModelBindingResult.NoResult;
@ -77,8 +62,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
internal async Task<ModelBindingResult> TryBindStrongModel<TModel>(
ModelBindingContext parentBindingContext,
string propertyName,
List<ModelValidationNode> childNodes)
string propertyName)
{
var propertyModelMetadata =
parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel));
@ -91,19 +75,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
propertyModelName,
model: null);
var modelBindingResult = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(
var result = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(
propertyBindingContext);
if (modelBindingResult != ModelBindingResult.NoResult)
if (result.IsModelSet)
{
if (modelBindingResult.ValidationNode != null)
{
childNodes.Add(modelBindingResult.ValidationNode);
}
return modelBindingResult;
return result;
}
else
{
return ModelBindingResult.Failed(propertyModelName);
}
return ModelBindingResult.Failed(propertyModelName);
}
}
}

View File

@ -309,22 +309,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelBindingResult = await modelBinder.BindModelAsync(modelBindingContext);
if (modelBindingResult.IsModelSet)
{
var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, modelBindingResult.Model);
var modelValidationContext = new ModelValidationContext(modelBindingContext, modelExplorer);
var validationNode = modelBindingResult.ValidationNode;
if (validationNode == null)
{
validationNode = new ModelValidationNode(
modelBindingResult.Key,
modelMetadata,
modelBindingResult.Model)
{
ValidateAllProperties = true,
};
}
objectModelValidator.Validate(modelValidationContext, validationNode);
objectModelValidator.Validate(
operationBindingContext.ValidatorProvider,
modelState,
modelBindingContext.ValidationState,
modelBindingResult.Key,
modelBindingResult.Model);
return modelState.IsValid;
}

View File

@ -52,16 +52,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var results = await BindPropertiesAsync(bindingContext, mutableObjectBinderContext.PropertyMetadata);
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
// Post-processing e.g. property setters and hooking up validation.
bindingContext.Model = model;
ProcessResults(bindingContext, results, validationNode);
ProcessResults(bindingContext, results);
return ModelBindingResult.Success(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.Success(bindingContext.ModelName, model);
}
/// <summary>
@ -398,10 +393,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
// Internal for testing.
internal ModelValidationNode ProcessResults(
internal void ProcessResults(
ModelBindingContext bindingContext,
IDictionary<ModelMetadata, ModelBindingResult> results,
ModelValidationNode validationNode)
IDictionary<ModelMetadata, ModelBindingResult> results)
{
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var modelExplorer =
@ -432,20 +426,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var result = entry.Value;
var propertyMetadata = entry.Key;
SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
var propertyValidationNode = result.ValidationNode;
if (propertyValidationNode == null)
{
// Make sure that irrespective of whether the properties of the model were bound with a value,
// create a validation node so that these get validated.
propertyValidationNode = new ModelValidationNode(result.Key, entry.Key, result.Model);
}
validationNode.ChildNodes.Add(propertyValidationNode);
}
}
return validationNode;
}
/// <summary>
@ -576,30 +558,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
// Returns true if validator execution adds a model error.
private static bool RunValidator(
IModelValidator validator,
ModelBindingContext bindingContext,
ModelExplorer propertyExplorer,
string modelStateKey)
{
var validationContext = new ModelValidationContext(bindingContext, propertyExplorer);
var addedError = false;
foreach (var validationResult in validator.Validate(validationContext))
{
bindingContext.ModelState.TryAddModelError(modelStateKey, validationResult.Message);
addedError = true;
}
if (!addedError)
{
bindingContext.ModelState.MarkFieldValid(modelStateKey);
}
return addedError;
}
internal sealed class PropertyValidationInfo
{
public PropertyValidationInfo()

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -30,13 +31,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = requestServices.GetRequiredService(bindingContext.ModelType);
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model)
{
SuppressValidation = true
};
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
}
}

View File

@ -57,12 +57,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
else
{
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
}
catch (Exception ex)

View File

@ -0,0 +1,123 @@
// 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;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// The default implementation of <see cref="IValidationStrategy"/> for a collection.
/// </summary>
/// <remarks>
/// This implementation handles cases like:
/// <example>
/// Model: IList&lt;Student&gt;
/// Query String: ?students[0].Age=8&amp;students[1].Age=9
///
/// In this case the elements of the collection are identified in the input data set by an incrementing
/// integer index.
/// </example>
///
/// or:
///
/// <example>
/// Model: IDictionary&lt;string, int&gt;
/// Query String: ?students[0].Key=Joey&amp;students[0].Value=8
///
/// In this case the dictionary is treated as a collection of key-value pairs, and the elements of the
/// collection are identified in the input data set by an incrementing integer index.
/// </example>
///
/// Using this key format, the enumerator enumerates model objects of type matching
/// <see cref="ModelMetadata.ElementMetadata"/>. The indices of the elements in the collection are used to
/// compute the model prefix keys.
/// </remarks>
public class DefaultCollectionValidationStrategy : IValidationStrategy
{
/// <summary>
/// Gets an instance of <see cref="DefaultCollectionValidationStrategy"/>.
/// </summary>
public static readonly IValidationStrategy Instance = new DefaultCollectionValidationStrategy();
private DefaultCollectionValidationStrategy()
{
}
/// <inheritdoc />
public IEnumerator<ValidationEntry> GetChildren(
ModelMetadata metadata,
string key,
object model)
{
return new Enumerator(metadata.ElementMetadata, key, (IEnumerable)model);
}
private class Enumerator : IEnumerator<ValidationEntry>
{
private readonly string _key;
private readonly ModelMetadata _metadata;
private readonly IEnumerable _model;
private readonly IEnumerator _enumerator;
private ValidationEntry _entry;
private int _index;
public Enumerator(
ModelMetadata metadata,
string key,
IEnumerable model)
{
_metadata = metadata;
_key = key;
_model = model;
_enumerator = _model.GetEnumerator();
_index = -1;
}
public ValidationEntry Current
{
get
{
return _entry;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
_index++;
if (!_enumerator.MoveNext())
{
return false;
}
var key = ModelNames.CreateIndexModelName(_key, _index);
var model = _enumerator.Current;
_entry = new ValidationEntry(_metadata, key, model);
return true;
}
public void Dispose()
{
}
public void Reset()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,98 @@
// 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;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// The default implementation of <see cref="IValidationStrategy"/> for a complex object.
/// </summary>
public class DefaultComplexObjectValidationStrategy : IValidationStrategy
{
/// <summary>
/// Gets an instance of <see cref="DefaultComplexObjectValidationStrategy"/>.
/// </summary>
public static readonly IValidationStrategy Instance = new DefaultComplexObjectValidationStrategy();
private DefaultComplexObjectValidationStrategy()
{
}
/// <inheritdoc />
public IEnumerator<ValidationEntry> GetChildren(
ModelMetadata metadata,
string key,
object model)
{
return new Enumerator(metadata.Properties, key, model);
}
private class Enumerator : IEnumerator<ValidationEntry>
{
private readonly string _key;
private readonly object _model;
private readonly ModelPropertyCollection _properties;
private ValidationEntry _entry;
private int _index;
public Enumerator(
ModelPropertyCollection properties,
string key,
object model)
{
_properties = properties;
_key = key;
_model = model;
_index = -1;
}
public ValidationEntry Current
{
get
{
return _entry;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
_index++;
if (_index >= _properties.Count)
{
return false;
}
var property = _properties[_index];
var propertyName = property.BinderModelName ?? property.PropertyName;
var key = ModelNames.CreatePropertyModelName(_key, propertyName);
var model = property.PropertyGetter(_model);
_entry = new ValidationEntry(property, key, model);
return true;
}
public void Dispose()
{
}
public void Reset()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -2,18 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Recursively validate an object.
/// The default implementation of <see cref="IObjectModelValidator"/>.
/// </summary>
public class DefaultObjectValidator : IObjectModelValidator
{
@ -27,324 +21,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
/// types to exclude from validation.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
public DefaultObjectValidator(
[NotNull] IList<IExcludeTypeValidationFilter> excludeFilters,
[NotNull] IModelMetadataProvider modelMetadataProvider)
IList<IExcludeTypeValidationFilter> excludeFilters,
IModelMetadataProvider modelMetadataProvider)
{
if (excludeFilters == null)
{
throw new ArgumentNullException(nameof(excludeFilters));
}
if (modelMetadataProvider == null)
{
throw new ArgumentNullException(nameof(modelMetadataProvider));
}
_modelMetadataProvider = modelMetadataProvider;
_excludeFilters = excludeFilters;
}
/// <inheritdoc />
public void Validate(
[NotNull] ModelValidationContext modelValidationContext,
[NotNull] ModelValidationNode validationNode)
IModelValidatorProvider validatorProvider,
ModelStateDictionary modelState,
ValidationStateDictionary validationState,
string prefix,
object model)
{
var validationContext = new ValidationContext()
if (validatorProvider == null)
{
ModelValidationContext = modelValidationContext,
Visited = new HashSet<object>(ReferenceEqualityComparer.Instance),
ValidationNode = validationNode
};
ValidateNonVisitedNodeAndChildren(
validationNode.Key,
validationContext,
validators: null);
}
private bool ValidateNonVisitedNodeAndChildren(
string modelKey,
ValidationContext validationContext,
IList<IModelValidator> validators)
{
// Recursion guard to avoid stack overflows
RuntimeHelpers.EnsureSufficientExecutionStack();
var modelValidationContext = validationContext.ModelValidationContext;
var modelExplorer = modelValidationContext.ModelExplorer;
var modelState = modelValidationContext.ModelState;
var currentValidationNode = validationContext.ValidationNode;
if (currentValidationNode.SuppressValidation)
{
// Short circuit if the node is marked to be suppressed.
// If there are any sub entries which were model bound, they need to be marked as skipped,
// Otherwise they will remain as unvalidated and the model state would be Invalid.
MarkChildNodesAsSkipped(modelKey, modelExplorer.Metadata, validationContext);
// For validation purposes this model is valid.
return true;
throw new ArgumentNullException(nameof(validatorProvider));
}
if (modelState.HasReachedMaxErrors)
if (modelState == null)
{
// Short circuit if max errors have been recorded. In which case we treat this as invalid.
return false;
throw new ArgumentNullException(nameof(modelState));
}
var isValid = true;
if (validators == null)
{
// 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.
validators = GetValidators(modelValidationContext.ValidatorProvider, modelExplorer.Metadata);
}
var visitor = new ValidationVisitor(
validatorProvider,
_excludeFilters,
modelState,
validationState);
// 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.ModelType;
if (IsTypeExcludedFromValidation(_excludeFilters, modelType))
{
var result = ShallowValidate(modelKey, modelExplorer, validationContext, validators);
// If there are any sub entries which were model bound, they need to be marked as skipped,
// Otherwise they will remain as unvalidated and the model state would be Invalid.
MarkChildNodesAsSkipped(modelKey, modelExplorer.Metadata, validationContext);
return result;
}
// 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);
isValid = ValidateChildNodes(modelKey, modelExplorer, validationContext);
if (isValid)
{
// Don't bother to validate this node if children failed.
isValid = ShallowValidate(modelKey, modelExplorer, validationContext, validators);
}
// Pop the object so that it can be validated again in a different path
validationContext.Visited.Remove(modelExplorer.Model);
return isValid;
}
private void MarkChildNodesAsSkipped(string currentModelKey, ModelMetadata metadata, ValidationContext validationContext)
{
var modelState = validationContext.ModelValidationContext.ModelState;
var fieldValidationState = modelState.GetFieldValidationState(currentModelKey);
// Since shallow validation is done, if the modelvalidation state is still marked as unvalidated,
// it is because some properties in the subtree are marked as unvalidated. Mark all such properties
// as skipped. Models which have their subtrees as Valid or Invalid do not need to be marked as skipped.
if (fieldValidationState != ModelValidationState.Unvalidated)
{
return;
}
// At this point we just want to mark all sub-entries present in the model state as skipped.
var entries = modelState.FindKeysWithPrefix(currentModelKey);
foreach (var entry in entries)
{
entry.Value.ValidationState = ModelValidationState.Skipped;
}
}
private IList<IModelValidator> GetValidators(IModelValidatorProvider provider, ModelMetadata metadata)
{
var validatorProviderContext = new ModelValidatorProviderContext(metadata);
provider.GetValidators(validatorProviderContext);
return validatorProviderContext
.Validators
.OrderBy(v => v, ValidatorOrderComparer.Instance)
.ToList();
}
private bool ValidateChildNodes(
string currentModelKey,
ModelExplorer modelExplorer,
ValidationContext validationContext)
{
var isValid = true;
var childNodes = GetChildNodes(validationContext, modelExplorer);
IList<IModelValidator> validators = null;
var elementMetadata = modelExplorer.Metadata.ElementMetadata;
if (elementMetadata != null)
{
validators = GetValidators(validationContext.ModelValidationContext.ValidatorProvider, elementMetadata);
}
foreach (var childNode in 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,
childModelExplorer),
Visited = validationContext.Visited,
ValidationNode = childNode
};
if (!ValidateNonVisitedNodeAndChildren(
childNode.Key,
propertyValidationContext,
validators))
{
isValid = false;
}
}
return isValid;
}
// Validates a single node (not including children)
// Returns true if validation passes successfully
private static bool ShallowValidate(
string modelKey,
ModelExplorer modelExplorer,
ValidationContext validationContext,
IList<IModelValidator> validators)
{
var isValid = true;
var modelState = validationContext.ModelValidationContext.ModelState;
var fieldValidationState = modelState.GetFieldValidationState(modelKey);
if (fieldValidationState == ModelValidationState.Invalid)
{
// Even if we have no validators it's possible that model binding may have added a
// validation error (conversion error, missing data). We want to still run
// validators even if that's the case.
isValid = false;
}
// 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.
if (validators == null || validators.Count > 0)
{
var modelValidationContext = ModelValidationContext.GetChildValidationContext(
validationContext.ModelValidationContext,
modelExplorer);
var modelValidationState = modelState.GetValidationState(modelKey);
// If either the model or its properties are unvalidated, validate them now.
if (modelValidationState == ModelValidationState.Unvalidated ||
fieldValidationState == ModelValidationState.Unvalidated)
{
foreach (var validator in validators)
{
foreach (var error in validator.Validate(modelValidationContext))
{
var errorKey = ModelNames.CreatePropertyModelName(modelKey, error.MemberName);
if (!modelState.TryAddModelError(errorKey, error.Message) &&
modelState.GetFieldValidationState(errorKey) == ModelValidationState.Unvalidated)
{
// If we are not able to add a model error
// for instance when the max error count is reached, mark the model as skipped.
modelState.MarkFieldSkipped(errorKey);
}
isValid = false;
}
}
}
}
// Add an entry only if there was an entry which was added by a model binder.
// This prevents adding spurious entries.
if (modelState.ContainsKey(modelKey) && isValid)
{
validationContext.ModelValidationContext.ModelState.MarkFieldValid(modelKey);
}
return isValid;
}
private bool IsTypeExcludedFromValidation(IList<IExcludeTypeValidationFilter> filters, Type type)
{
return filters.Any(filter => filter.IsTypeExcluded(type));
}
private IList<ModelValidationNode> GetChildNodes(ValidationContext context, ModelExplorer modelExplorer)
{
var validationNode = context.ValidationNode;
// This is the trivial case where the node-tree that was built-up during binding already has
// all of the nodes we need.
if (validationNode.ChildNodes.Count != 0 ||
!validationNode.ValidateAllProperties ||
validationNode.Model == null)
{
return validationNode.ChildNodes;
}
var childNodes = new List<ModelValidationNode>(validationNode.ChildNodes);
var elementMetadata = modelExplorer.Metadata.ElementMetadata;
if (elementMetadata == null)
{
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
};
childNodes.Add(childNode);
}
}
else
{
var enumerableModel = (IEnumerable)modelExplorer.Model;
// 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
};
childNodes.Add(childNode);
index++;
}
}
return childNodes;
}
private class ValidationContext
{
public ModelValidationContext ModelValidationContext { get; set; }
public HashSet<object> Visited { get; set; }
public ModelValidationNode ValidationNode { get; set; }
}
// Sorts validators based on whether or not they are 'required'. We want to run
// 'required' validators first so that we get the best possible error message.
private class ValidatorOrderComparer : IComparer<IModelValidator>
{
public static readonly ValidatorOrderComparer Instance = new ValidatorOrderComparer();
public int Compare(IModelValidator x, IModelValidator y)
{
var xScore = x.IsRequired ? 0 : 1;
var yScore = y.IsRequired ? 0 : 1;
return xScore.CompareTo(yScore);
}
var metadata = model == null ? null : _modelMetadataProvider.GetMetadataForType(model.GetType());
visitor.Validate(metadata, prefix, model);
}
}
}

View File

@ -0,0 +1,128 @@
// 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;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// An implementation of <see cref="IValidationStrategy"/> for a collection bound using 'explict indexing'
/// style keys.
/// </summary>
/// <remarks>
/// This implemenation handles cases like:
/// <example>
/// Model: IList&lt;Student&gt;
/// Query String: ?students.index=Joey,Katherine&amp;students[Joey].Age=8&amp;students[Katherine].Age=9
///
/// In this case, 'Joey' and 'Katherine' need to be used in the model prefix keys, but cannot be inferred
/// form inspecting the collection. These prefixes are captured during model binding, and mapped to
/// the corresponding ordinal index of a model object in the collection. The enumerator returned from this
/// class will yield two 'Student' objects with corresponding keys 'students[Joey]' and 'students[Katherine]'.
/// </example>
///
/// Using this key format, the enumerator enumerates model objects of type matching
/// <see cref="ModelMetadata.ElementMetadata"/>. The keys captured during model binding are mapped to the elements
/// in the collection to compute the model prefix keys.
/// </remarks>
public class ExplicitIndexCollectionValidationStrategy : IValidationStrategy
{
/// <summary>
/// Creates a new <see cref="ExplicitIndexCollectionValidationStrategy"/>.
/// </summary>
/// <param name="elementKeys">The keys of collection elements that were used during model binding.</param>
public ExplicitIndexCollectionValidationStrategy(IEnumerable<string> elementKeys)
{
if (elementKeys == null)
{
throw new ArgumentNullException(nameof(elementKeys));
}
ElementKeys = elementKeys;
}
/// <summary>
/// Gets the keys of collection elements that were used during model binding.
/// </summary>
public IEnumerable<string> ElementKeys { get; }
/// <inheritdoc />
public IEnumerator<ValidationEntry> GetChildren(
ModelMetadata metadata,
string key,
object model)
{
return new Enumerator(metadata.ElementMetadata, key, ElementKeys, (IEnumerable)model);
}
private class Enumerator : IEnumerator<ValidationEntry>
{
private readonly string _key;
private readonly ModelMetadata _metadata;
private readonly IEnumerator _enumerator;
private readonly IEnumerator<string> _keyEnumerator;
private ValidationEntry _entry;
public Enumerator(
ModelMetadata metadata,
string key,
IEnumerable<string> elementKeys,
IEnumerable model)
{
_metadata = metadata;
_key = key;
_keyEnumerator = elementKeys.GetEnumerator();
_enumerator = model.GetEnumerator();
}
public ValidationEntry Current
{
get
{
return _entry;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
if (!_keyEnumerator.MoveNext())
{
return false;
}
if (!_enumerator.MoveNext())
{
return false;
}
var model = _enumerator.Current;
var key = ModelNames.CreateIndexModelName(_key, _keyEnumerator.Current);
_entry = new ValidationEntry(_metadata, key, model);
return true;
}
public void Dispose()
{
}
public void Reset()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -1,6 +1,8 @@
// 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 Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
@ -9,12 +11,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
public interface IObjectModelValidator
{
/// <summary>
/// Validates the given model in <see cref="ModelValidationContext.ModelExplorer"/>.
/// Validates the provided object.
/// </summary>
/// <param name="validationContext">The <see cref="ModelValidationContext"/> associated with the current call.
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
/// <param name="modelState">The <see cref="ModelStateDictionary"/>.</param>
/// <param name="validationState">The <see cref="ValidationStateDictionary"/>. May be null.</param>
/// <param name="prefix">
/// The model prefix. Used to map the model object to entries in <paramref name="modelState"/>.
/// </param>
/// <param name="validationNode">The <see cref="ModelValidationNode"/> for the model which gets validated.
/// </param>
void Validate(ModelValidationContext validationContext, ModelValidationNode validationNode);
/// <param name="model">The model object.</param>
void Validate(
IModelValidatorProvider validatorProvider,
ModelStateDictionary modelState,
ValidationStateDictionary validationState,
string prefix,
object model);
}
}

View File

@ -0,0 +1,135 @@
// 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;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// An implementation of <see cref="IValidationStrategy"/> for a dictionary bound with 'short form' style keys.
/// </summary>
/// <typeparam name="TKey">The <see cref="Type"/> of the keys of the model dictionary.</typeparam>
/// <typeparam name="TValue">The <see cref="Type"/> of the values of the model dictionary.</typeparam>
/// <remarks>
/// This implemenation handles cases like:
/// <example>
/// Model: IDictionary&lt;string, Student&gt;
/// Query String: ?students[Joey].Age=8&amp;students[Katherine].Age=9
///
/// In this case, 'Joey' and 'Katherine' are the keys of the dictionary, used to bind two 'Student'
/// objects. The enumerator returned from this class will yield two 'Student' objects with corresponding
/// keys 'students[Joey]' and 'students[Katherine]'
/// </example>
///
/// Using this key format, the enumerator enumerates model objects of type <typeparamref name="TValue"/>. The
/// keys of the dictionary are not validated as they must be simple types.
/// </remarks>
public class ShortFormDictionaryValidationStrategy<TKey, TValue> : IValidationStrategy
{
private readonly ModelMetadata _valueMetadata;
/// <summary>
/// Creates a new <see cref="ShortFormDictionaryValidationStrategy{TKey, TValue}"/>.
/// </summary>
/// <param name="keyMappings">The mapping from model prefix key to dictionary key.</param>
/// <param name="valueMetadata">
/// The <see cref="ModelMetadata"/> associated with <typeparamref name="TValue"/>.
/// </param>
public ShortFormDictionaryValidationStrategy(
IEnumerable<KeyValuePair<string, TKey>> keyMappings,
ModelMetadata valueMetadata)
{
KeyMappings = keyMappings;
_valueMetadata = valueMetadata;
}
/// <summary>
/// Gets the mapping from model prefix key to dictionary key.
/// </summary>
public IEnumerable<KeyValuePair<string, TKey>> KeyMappings { get; }
/// <inheritdoc />
public IEnumerator<ValidationEntry> GetChildren(
ModelMetadata metadata,
string key,
object model)
{
return new Enumerator(_valueMetadata, key, KeyMappings, (IDictionary<TKey, TValue>)model);
}
private class Enumerator : IEnumerator<ValidationEntry>
{
private readonly string _key;
private readonly ModelMetadata _metadata;
private readonly IDictionary<TKey, TValue> _model;
private readonly IEnumerator<KeyValuePair<string, TKey>> _keyMappingEnumerator;
private ValidationEntry _entry;
public Enumerator(
ModelMetadata metadata,
string key,
IEnumerable<KeyValuePair<string, TKey>> keyMappings,
IDictionary<TKey, TValue> model)
{
_metadata = metadata;
_key = key;
_model = model;
_keyMappingEnumerator = keyMappings.GetEnumerator();
}
public ValidationEntry Current
{
get
{
return _entry;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
TValue value;
while (true)
{
if (!_keyMappingEnumerator.MoveNext())
{
return false;
}
if (_model.TryGetValue(_keyMappingEnumerator.Current.Value, out value))
{
// Skip over entries that we can't find in the dictionary, they will show up as unvalidated.
break;
}
}
var key = ModelNames.CreateIndexModelName(_key, _keyMappingEnumerator.Current.Key);
var model = value;
_entry = new ValidationEntry(_metadata, key, model);
return true;
}
public void Dispose()
{
}
public void Reset()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,366 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// A visitor implementation that interprets <see cref="ValidationStateDictionary"/> to traverse
/// a model object graph and perform validation.
/// </summary>
public class ValidationVisitor
{
private readonly IModelValidatorProvider _validatorProvider;
private readonly IList<IExcludeTypeValidationFilter> _excludeFilters;
private readonly ModelStateDictionary _modelState;
private readonly ValidationStateDictionary _validationState;
private object _container;
private string _key;
private object _model;
private ModelMetadata _metadata;
private IValidationStrategy _strategy;
private HashSet<object> _currentPath;
/// <summary>
/// Creates a new <see cref="ValidationVisitor"/>.
/// </summary>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
/// <param name="excludeFilters">The list of <see cref="IExcludeTypeValidationFilter"/>.</param>
/// <param name="modelState">The <see cref="ModelStateDictionary"/>.</param>
/// <param name="validationState">The <see cref="ValidationStateDictionary"/>.</param>
public ValidationVisitor(
IModelValidatorProvider validatorProvider,
IList<IExcludeTypeValidationFilter> excludeFilters,
ModelStateDictionary modelState,
ValidationStateDictionary validationState)
{
if (validatorProvider == null)
{
throw new ArgumentNullException(nameof(validatorProvider));
}
if (excludeFilters == null)
{
throw new ArgumentNullException(nameof(excludeFilters));
}
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
_validatorProvider = validatorProvider;
_excludeFilters = excludeFilters;
_modelState = modelState;
_validationState = validationState;
_currentPath = new HashSet<object>(ReferenceEqualityComparer.Instance);
}
/// <summary>
/// Validates a object.
/// </summary>
/// <param name="metadata">The <see cref="ModelMetadata"/> associated with the model.</param>
/// <param name="key">The model prefix key.</param>
/// <param name="model">The model object.</param>
/// <returns><c>true</c> if the object is valid, otherwise <c>false</c>.</returns>
public bool Validate(ModelMetadata metadata, string key, object model)
{
if (model == null)
{
if (_modelState.GetValidationState(key) != ModelValidationState.Valid)
{
_modelState.MarkFieldValid(key);
}
return true;
}
return Visit(metadata, key, model);
}
/// <summary>
/// Validates a single node in a model object graph.
/// </summary>
/// <returns><c>true</c> if the node is valid, otherwise <c>false</c>.</returns>
protected virtual bool ValidateNode()
{
var state = _modelState.GetValidationState(_key);
if (state == ModelValidationState.Unvalidated)
{
var validators = GetValidators(_metadata);
var count = validators.Count;
if (count > 0)
{
var context = new ModelValidationContext()
{
Container = _container,
Model = _model,
Metadata = _metadata,
};
var results = new List<ModelValidationResult>();
for (var i = 0; i < count; i++)
{
results.AddRange(validators[i].Validate(context));
}
var resultsCount = results.Count;
for (var i = 0; i < resultsCount; i++)
{
var result = results[i];
var key = ModelNames.CreatePropertyModelName(_key, result.MemberName);
_modelState.TryAddModelError(key, result.Message);
}
}
}
state = _modelState.GetFieldValidationState(_key);
if (state == ModelValidationState.Invalid)
{
return false;
}
else
{
// If the field has an entry in ModelState, then record it as valid. Don't create
// extra entries if they don't exist already.
var entry = _modelState[_key];
if (entry != null)
{
entry.ValidationState = ModelValidationState.Valid;
}
return true;
}
}
private bool Visit(ModelMetadata metadata, string key, object model)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
if (model != null && !_currentPath.Add(model))
{
// This is a cycle, bail.
return true;
}
var entry = GetValidationEntry(model);
key = entry?.Key ?? key ?? string.Empty;
metadata = entry?.Metadata ?? metadata;
var strategy = entry?.Strategy;
if (_modelState.HasReachedMaxErrors)
{
SuppressValidation(key);
return false;
}
else if ((entry != null && entry.SuppressValidation))
{
SuppressValidation(key);
return true;
}
using (StateManager.Recurse(this, key, metadata, model, strategy))
{
if (_metadata.IsEnumerableType)
{
return VisitEnumerableType();
}
else if (_metadata.IsComplexType)
{
return VisitComplexType();
}
else
{
return VisitSimpleType();
}
}
}
private bool VisitEnumerableType()
{
var isValid = true;
if (_model != null)
{
var strategy = _strategy ?? DefaultCollectionValidationStrategy.Instance;
isValid = VisitChildren(strategy);
}
// Double-checking HasReachedMaxErrors just in case this model has no elements.
if (isValid && !_modelState.HasReachedMaxErrors)
{
isValid &= ValidateNode();
}
return isValid;
}
private bool VisitComplexType()
{
var isValid = true;
if (_model != null && ShouldValidateProperties(_metadata))
{
var strategy = _strategy ?? DefaultComplexObjectValidationStrategy.Instance;
isValid = VisitChildren(strategy);
}
else if (_model != null)
{
// Suppress validation for the prefix, but we still want to validate the object.
SuppressValidation(_key);
}
// Double-checking HasReachedMaxErrors just in case this model has no properties.
if (isValid && !_modelState.HasReachedMaxErrors)
{
isValid &= ValidateNode();
}
return isValid;
}
private bool VisitSimpleType()
{
if (_modelState.HasReachedMaxErrors)
{
SuppressValidation(_key);
return false;
}
return ValidateNode();
}
private bool VisitChildren(IValidationStrategy strategy)
{
var isValid = true;
var enumerator = strategy.GetChildren(_metadata, _key, _model);
while (enumerator.MoveNext())
{
var metadata = enumerator.Current.Metadata;
var model = enumerator.Current.Model;
var key = enumerator.Current.Key;
isValid &= Visit(metadata, key, model);
}
return isValid;
}
private IList<IModelValidator> GetValidators(ModelMetadata metadata)
{
var context = new ModelValidatorProviderContext(metadata);
_validatorProvider.GetValidators(context);
return context.Validators.OrderBy(v => v, ValidatorOrderComparer.Instance).ToList();
}
private void SuppressValidation(string key)
{
var entries = _modelState.FindKeysWithPrefix(key);
foreach (var entry in entries)
{
entry.Value.ValidationState = ModelValidationState.Skipped;
}
}
private bool ShouldValidateProperties(ModelMetadata metadata)
{
var count = _excludeFilters.Count;
for (var i = 0; i < _excludeFilters.Count; i++)
{
if (_excludeFilters[i].IsTypeExcluded(metadata.UnderlyingOrModelType))
{
return false;
}
}
return true;
}
private ValidationStateEntry GetValidationEntry(object model)
{
if (model == null || _validationState == null)
{
return null;
}
ValidationStateEntry entry;
_validationState.TryGetValue(model, out entry);
return entry;
}
private struct StateManager : IDisposable
{
private readonly ValidationVisitor _visitor;
private readonly object _container;
private readonly string _key;
private readonly ModelMetadata _metadata;
private readonly object _model;
private readonly object _newModel;
private readonly IValidationStrategy _strategy;
public static StateManager Recurse(
ValidationVisitor visitor,
string key,
ModelMetadata metadata,
object model,
IValidationStrategy strategy)
{
var recursifier = new StateManager(visitor, model);
visitor._container = visitor._model;
visitor._key = key;
visitor._metadata = metadata;
visitor._model = model;
visitor._strategy = strategy;
return recursifier;
}
public StateManager(ValidationVisitor visitor, object newModel)
{
_visitor = visitor;
_newModel = newModel;
_container = _visitor._container;
_key = _visitor._key;
_metadata = _visitor._metadata;
_model = _visitor._model;
_strategy = _visitor._strategy;
}
public void Dispose()
{
_visitor._container = _container;
_visitor._key = _key;
_visitor._metadata = _metadata;
_visitor._model = _model;
_visitor._strategy = _strategy;
_visitor._currentPath.Remove(_newModel);
}
}
// Sorts validators based on whether or not they are 'required'. We want to run
// 'required' validators first so that we get the best possible error message.
private class ValidatorOrderComparer : IComparer<IModelValidator>
{
public static readonly ValidatorOrderComparer Instance = new ValidatorOrderComparer();
public int Compare(IModelValidator x, IModelValidator y)
{
var xScore = x.IsRequired ? 0 : 1;
var yScore = y.IsRequired ? 0 : 1;
return xScore.CompareTo(yScore);
}
}
}
}

View File

@ -33,20 +33,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext validationContext)
{
var modelExplorer = validationContext.ModelExplorer;
var metadata = modelExplorer.Metadata;
var metadata = validationContext.Metadata;
var memberName = metadata.PropertyName ?? metadata.ModelType.Name;
var containerExplorer = modelExplorer.Container;
var container = validationContext.Container;
var container = containerExplorer?.Model;
var context = new ValidationContext(container ?? modelExplorer.Model)
var context = new ValidationContext(container ?? validationContext.Model)
{
DisplayName = metadata.GetDisplayName(),
MemberName = memberName
};
var result = Attribute.GetValidationResult(modelExplorer.Model, context);
var result = Attribute.GetValidationResult(validationContext.Model, context);
if (result != ValidationResult.Success)
{
// ModelValidationResult.MemberName is used by invoking validators (such as ModelValidator) to

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
{
var model = context.ModelExplorer.Model;
var model = context.Model;
if (model == null)
{
return Enumerable.Empty<ModelValidationResult>();

View File

@ -1514,18 +1514,12 @@ namespace Microsoft.AspNet.Mvc
MetadataProvider,
modelName);
var validationContext = new ModelValidationContext(
bindingSource: null,
validatorProvider: BindingContext.ValidatorProvider,
modelState: ModelState,
modelExplorer: modelExplorer);
ObjectValidator.Validate(
validationContext,
new ModelValidationNode(modelName, modelExplorer.Metadata, model)
{
ValidateAllProperties = true
});
BindingContext.ValidatorProvider,
ModelState,
validationState: null,
prefix: prefix,
model: model);
return ModelState.IsValid;
}

View File

@ -417,18 +417,14 @@ namespace System.Web.Http
{
var modelExplorer = MetadataProvider.GetModelExplorerForType(typeof(TEntity), entity);
var modelValidationContext = new ModelValidationContext(
bindingSource: null,
validatorProvider: BindingContext.ValidatorProvider,
modelState: ModelState,
modelExplorer: modelExplorer);
var validatidationState = new ValidationStateDictionary();
ObjectValidator.Validate(
modelValidationContext,
new ModelValidationNode(keyPrefix, modelExplorer.Metadata, entity)
{
ValidateAllProperties = true
});
BindingContext.ValidatorProvider,
ModelState,
validatidationState,
keyPrefix,
entity);
}
protected virtual void Dispose(bool disposing)

View File

@ -4,6 +4,7 @@
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
@ -18,13 +19,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
if (bindingContext.ModelType == typeof(HttpRequestMessage))
{
var model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model)
{
SuppressValidation = true,
};
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode);
bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true });
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
return ModelBindingResult.NoResultAsync;

View File

@ -115,7 +115,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
{
context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string));
})
.Returns(ModelBindingResult.SuccessAsync(string.Empty, value, validationNode: null));
.Returns(ModelBindingResult.SuccessAsync(string.Empty, value));
var actionContext = new ActionContext(
new DefaultHttpContext(),
@ -153,19 +153,30 @@ namespace Microsoft.AspNet.Mvc.Controllers
var actionContext = GetActionContext(actionDescriptor);
var actionBindingContext = GetActionBindingContext();
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidatorProvider
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
.Verifiable();
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
var argumentBinder = GetArgumentBinder(mockValidator.Object);
// Act
var result = await argumentBinder
.BindActionArgumentsAsync(actionContext, actionBindingContext, new TestController());
// Assert
mockValidatorProvider.Verify(
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()), Times.Once());
mockValidator
.Verify(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()),
Times.Once());
}
[Fact]
@ -197,19 +208,29 @@ namespace Microsoft.AspNet.Mvc.Controllers
ModelBinder = binder.Object,
};
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidatorProvider
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
.Verifiable();
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
var argumentBinder = GetArgumentBinder(mockValidator.Object);
// Act
var result = await argumentBinder
.BindActionArgumentsAsync(actionContext, actionBindingContext, new TestController());
// Assert
mockValidatorProvider.Verify(
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()),
mockValidator
.Verify(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()),
Times.Never());
}
@ -228,19 +249,30 @@ namespace Microsoft.AspNet.Mvc.Controllers
var actionContext = GetActionContext(actionDescriptor);
var actionBindingContext = GetActionBindingContext();
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidatorProvider
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
.Verifiable();
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
var argumentBinder = GetArgumentBinder(mockValidator.Object);
// Act
var result = await argumentBinder
.BindActionArgumentsAsync(actionContext, actionBindingContext, new TestController());
// Assert
mockValidatorProvider.Verify(
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()), Times.Once());
mockValidator
.Verify(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()),
Times.Once());
}
[Fact]
@ -271,20 +303,29 @@ namespace Microsoft.AspNet.Mvc.Controllers
ModelBinder = binder.Object,
};
var mockValidatorProvider = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidatorProvider
.Setup(o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()))
.Verifiable();
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
var argumentBinder = GetArgumentBinder(mockValidatorProvider.Object);
var argumentBinder = GetArgumentBinder(mockValidator.Object);
// Act
var result = await argumentBinder
.BindActionArgumentsAsync(actionContext, actionBindingContext, new TestController());
// Assert
mockValidatorProvider.Verify(
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()),
mockValidator
.Verify(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()),
Times.Never());
}
@ -363,7 +404,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
var binder = new Mock<IModelBinder>();
binder
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(ModelBindingResult.SuccessAsync(string.Empty, model: null, validationNode: null));
.Returns(ModelBindingResult.SuccessAsync(string.Empty, model: null));
var actionBindingContext = new ActionBindingContext()
{
@ -401,7 +442,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
var binder = new Mock<IModelBinder>();
binder
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(ModelBindingResult.SuccessAsync(key: string.Empty, model: null, validationNode: null));
.Returns(ModelBindingResult.SuccessAsync(key: string.Empty, model: null));
var actionBindingContext = new ActionBindingContext()
{
@ -545,7 +586,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
object model;
if (inputPropertyValues.TryGetValue(bindingContext.ModelName, out model))
{
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode: null);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
else
{
@ -600,8 +641,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns<ModelBindingContext>(mbc =>
{
var validationNode = new ModelValidationNode(string.Empty, mbc.ModelMetadata, model);
return ModelBindingResult.SuccessAsync(string.Empty, model, validationNode: validationNode);
return ModelBindingResult.SuccessAsync(string.Empty, model);
});
return new ActionBindingContext()
@ -614,10 +654,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
{
if (validator == null)
{
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator.Setup(
o => o.Validate(It.IsAny<ModelValidationContext>(), It.IsAny<ModelValidationNode>()));
validator = mockValidator.Object;
validator = CreateMockValidator();
}
return new DefaultControllerActionArgumentBinder(
@ -625,6 +662,19 @@ namespace Microsoft.AspNet.Mvc.Controllers
validator);
}
private static IObjectModelValidator CreateMockValidator()
{
var mockValidator = new Mock<IObjectModelValidator>(MockBehavior.Strict);
mockValidator
.Setup(o => o.Validate(
It.IsAny<IModelValidatorProvider>(),
It.IsAny<ModelStateDictionary>(),
It.IsAny<ValidationStateDictionary>(),
It.IsAny<string>(),
It.IsAny<object>()));
return mockValidator.Object;
}
// No need for bind-related attributes on properties in this controller class. Properties are added directly
// to the BoundProperties collection, bypassing usual requirements.
private class TestController

View File

@ -62,10 +62,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Empty(Assert.IsType<string[]>(result.Model));
Assert.Equal("modelName", result.Key);
Assert.True(result.IsModelSet);
Assert.Same(result.ValidationNode.Model, result.Model);
Assert.Same(result.ValidationNode.Key, result.Key);
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
}
[Theory]
@ -173,7 +169,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
if (value != ValueProviderResult.None)
{
var model = value.ConvertTo(mbc.ModelType);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model, validationNode: null);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model);
}
return ModelBindingResult.NoResultAsync;
});

View File

@ -67,9 +67,6 @@ 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]
@ -141,9 +138,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, _model);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, _model, validationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, _model);
}
}
}

View File

@ -50,13 +50,6 @@ 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]
@ -78,7 +71,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Returns non-null because it understands the metadata type.
Assert.NotNull(binderResult);
Assert.False(binderResult.IsModelSet);
Assert.Null(binderResult.ValidationNode);
Assert.Null(binderResult.Model);
// Key is empty because this was a top-level binding.
@ -104,7 +96,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
Assert.NotNull(binderResult);
Assert.False(binderResult.IsModelSet);
Assert.Null(binderResult.ValidationNode);
}
[Fact]
@ -172,7 +163,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Returns non-null because it understands the metadata type.
Assert.NotNull(binderResult);
Assert.False(binderResult.IsModelSet);
Assert.Null(binderResult.ValidationNode);
Assert.Null(binderResult.Model);
// Key is empty because this was a top-level binding.
@ -209,7 +199,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.NotNull(binderResult);
Assert.False(binderResult.IsModelSet);
Assert.Null(binderResult.Model);
Assert.Null(binderResult.ValidationNode);
// Key is empty because this was a top-level binding.
var entry = Assert.Single(bindingContext.ModelState);

View File

@ -25,8 +25,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal(bindingContext.OperationBindingContext.HttpContext.RequestAborted, result.Model);
Assert.NotNull(result.ValidationNode);
Assert.True(result.ValidationNode.SuppressValidation);
}
[Theory]

View File

@ -9,6 +9,7 @@ using System.Linq;
#endif
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
#if DNX451
using Moq;
#endif
@ -32,13 +33,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var binder = new CollectionModelBinder<int>();
// Act
var boundCollection = await binder.BindComplexCollectionFromIndexes(bindingContext, new[] { "foo", "bar", "baz" });
var collectionResult = await binder.BindComplexCollectionFromIndexes(
bindingContext,
new[] { "foo", "bar", "baz" });
// Assert
Assert.Equal(new[] { 42, 0, 200 }, boundCollection.Model.ToArray());
Assert.Equal(
new[] { "someName[foo]", "someName[baz]" },
boundCollection.ValidationNode.ChildNodes.Select(o => o.Key).ToArray());
Assert.Equal(new[] { 42, 0, 200 }, collectionResult.Model.ToArray());
// This requires a non-default IValidationStrategy
var strategy = Assert.IsType<ExplicitIndexCollectionValidationStrategy>(collectionResult.ValidationStrategy);
Assert.Equal(new[] { "foo", "bar", "baz" }, strategy.ElementKeys);
}
[Fact]
@ -59,9 +63,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.Equal(new[] { 42, 100 }, boundCollection.Model.ToArray());
Assert.Equal(
new[] { "someName[0]", "someName[1]" },
boundCollection.ValidationNode.ChildNodes.Select(o => o.Key).ToArray());
// This uses the default IValidationStrategy
Assert.DoesNotContain(boundCollection, bindingContext.ValidationState.Keys);
}
[Theory]
@ -197,7 +201,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.NotNull(result.Model);
Assert.NotNull(result.ValidationNode);
var model = Assert.IsType<List<int>>(result.Model);
Assert.Empty(model);
@ -252,10 +255,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Empty(Assert.IsType<List<string>>(result.Model));
Assert.Equal("modelName", result.Key);
Assert.True(result.IsModelSet);
Assert.Same(result.ValidationNode.Model, result.Model);
Assert.Same(result.ValidationNode.Key, result.Key);
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
}
// Setup like CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject except
@ -290,10 +289,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Empty(list);
Assert.Equal("modelName", result.Key);
Assert.True(result.IsModelSet);
Assert.Same(result.ValidationNode.Model, result.Model);
Assert.Same(result.ValidationNode.Key, result.Key);
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
}
[Theory]
@ -361,14 +356,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Arrange
var culture = new CultureInfo("fr-FR");
var bindingContext = GetModelBindingContext(new SimpleValueProvider());
ModelValidationNode childValidationNode = null;
Mock.Get<IModelBinder>(bindingContext.OperationBindingContext.ModelBinder)
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext mbc) =>
{
Assert.Equal("someName", mbc.ModelName);
childValidationNode = new ModelValidationNode("someName", mbc.ModelMetadata, mbc.Model);
return ModelBindingResult.SuccessAsync(mbc.ModelName, 42, childValidationNode);
return ModelBindingResult.SuccessAsync(mbc.ModelName, 42);
});
var modelBinder = new CollectionModelBinder<int>();
@ -379,7 +373,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.Equal(new[] { 42 }, boundCollection.Model.ToArray());
Assert.Equal(new[] { childValidationNode }, boundCollection.ValidationNode.ChildNodes.ToArray());
}
private static ModelBindingContext GetModelBindingContext(
@ -399,7 +392,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
ModelBinder = CreateIntBinder(),
MetadataProvider = metadataProvider
}
},
ValidationState = new ValidationStateDictionary(),
};
return bindingContext;
@ -425,8 +419,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
}
else
{
var validationNode = new ModelValidationNode(mbc.ModelName, mbc.ModelMetadata, model);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model);
}
});
return mockIntBinder.Object;

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Moq;
using Xunit;
@ -28,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
{ "someName", "dummyValue" }
},
ValidationState = new ValidationStateDictionary(),
};
var mockIntBinder = new Mock<IModelBinder>();
@ -40,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal("someName", context.ModelName);
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
return ModelBindingResult.SuccessAsync("someName", 42, validationNode: null);
return ModelBindingResult.SuccessAsync("someName", 42);
});
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
@ -53,6 +55,96 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal(42, result.Model);
}
[Fact]
public async Task BindModel_SuccessfulBind_SetsValidationStateAtTopLevel()
{
// Arrange
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = true,
IsTopLevelObject = true,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(int)),
ModelName = "someName",
ModelState = new ModelStateDictionary(),
OperationBindingContext = new OperationBindingContext(),
ValueProvider = new SimpleValueProvider
{
{ "someName", "dummyValue" }
},
ValidationState = new ValidationStateDictionary(),
};
var mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(
delegate (ModelBindingContext context)
{
Assert.Same(bindingContext.ModelMetadata, context.ModelMetadata);
Assert.Equal("someName", context.ModelName);
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
return ModelBindingResult.SuccessAsync("someName", 42);
});
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
// Act
var result = await shimBinder.BindModelAsync(bindingContext);
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal(42, result.Model);
Assert.Contains(result.Model, bindingContext.ValidationState.Keys);
var entry = bindingContext.ValidationState[result.Model];
Assert.Equal("someName", entry.Key);
Assert.Same(bindingContext.ModelMetadata, entry.Metadata);
}
[Fact]
public async Task BindModel_SuccessfulBind_DoesNotSetValidationState_WhenNotTopLevel()
{
// Arrange
var bindingContext = new ModelBindingContext
{
FallbackToEmptyPrefix = true,
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(int)),
ModelName = "someName",
ModelState = new ModelStateDictionary(),
OperationBindingContext = new OperationBindingContext(),
ValueProvider = new SimpleValueProvider
{
{ "someName", "dummyValue" }
},
ValidationState = new ValidationStateDictionary(),
};
var mockIntBinder = new Mock<IModelBinder>();
mockIntBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(
delegate (ModelBindingContext context)
{
Assert.Same(bindingContext.ModelMetadata, context.ModelMetadata);
Assert.Equal("someName", context.ModelName);
Assert.Same(bindingContext.ValueProvider, context.ValueProvider);
return ModelBindingResult.SuccessAsync("someName", 42);
});
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
// Act
var result = await shimBinder.BindModelAsync(bindingContext);
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal(42, result.Model);
Assert.Empty(bindingContext.ValidationState);
}
[Fact]
public async Task BindModel_SuccessfulBind_ComplexTypeFallback_ReturnsModel()
{
@ -71,6 +163,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
{ "someOtherName", "dummyValue" }
},
ValidationState = new ValidationStateDictionary(),
};
var mockIntBinder = new Mock<IModelBinder>();
@ -88,7 +181,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal("", mbc.ModelName);
Assert.Same(bindingContext.ValueProvider, mbc.ValueProvider);
return ModelBindingResult.SuccessAsync(string.Empty, expectedModel, validationNode: null);
return ModelBindingResult.SuccessAsync(string.Empty, expectedModel);
});
var shimBinder = CreateCompositeBinder(mockIntBinder.Object);
@ -223,7 +316,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var modelBinder = new Mock<IModelBinder>();
modelBinder
.Setup(mb => mb.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns(ModelBindingResult.SuccessAsync("someName", model: null, validationNode: null));
.Returns(ModelBindingResult.SuccessAsync("someName", model: null));
var composite = CreateCompositeBinder(modelBinder.Object);
@ -308,26 +401,6 @@ 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]
@ -415,15 +488,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var valueProvider = new SimpleValueProvider();
ModelValidationNode validationNode = null;
var mockBinder = new Mock<IModelBinder>();
mockBinder
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
.Returns((ModelBindingContext context) =>
{
validationNode = new ModelValidationNode("someName", context.ModelMetadata, 42);
return ModelBindingResult.SuccessAsync("someName", 42, validationNode);
return ModelBindingResult.SuccessAsync("someName", 42);
});
var binder = CreateCompositeBinder(mockBinder.Object);
@ -435,7 +506,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Same(validationNode, result.ValidationNode);
}
private static ModelBindingContext CreateBindingContext(
@ -456,7 +526,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
MetadataProvider = metadataProvider,
ModelBinder = binder,
}
},
ValidationState = new ValidationStateDictionary(),
};
return bindingContext;
}

View File

@ -8,6 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Primitives;
using Moq;
using Xunit;
@ -45,6 +46,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal(2, dictionary.Count);
Assert.Equal("forty-two", dictionary[42]);
Assert.Equal("eighty-four", dictionary[84]);
// This uses the default IValidationStrategy
Assert.DoesNotContain(result.Model, bindingContext.ValidationState.Keys);
}
[Theory]
@ -78,6 +82,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal(2, dictionary.Count);
Assert.Equal("forty-two", dictionary[42]);
Assert.Equal("eighty-four", dictionary[84]);
// This uses the default IValidationStrategy
Assert.DoesNotContain(result.Model, bindingContext.ValidationState.Keys);
}
// modelName, keyFormat, dictionary
@ -135,7 +142,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal(modelName, result.Key);
Assert.NotNull(result.ValidationNode);
var resultDictionary = Assert.IsAssignableFrom<IDictionary<string, string>>(result.Model);
Assert.Equal(dictionary, resultDictionary);
@ -172,7 +178,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal("prefix", result.Key);
Assert.NotNull(result.ValidationNode);
var resultDictionary = Assert.IsAssignableFrom<IDictionary<string, string>>(result.Model);
Assert.Empty(resultDictionary);
@ -223,7 +228,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal("prefix", result.Key);
Assert.NotNull(result.ValidationNode);
var resultDictionary = Assert.IsAssignableFrom<IDictionary<long, int>>(result.Model);
Assert.Equal(dictionary, resultDictionary);
@ -264,10 +268,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal("prefix", result.Key);
Assert.NotNull(result.ValidationNode);
var resultDictionary = Assert.IsAssignableFrom<IDictionary<int, ModelWithProperties>>(result.Model);
Assert.Equal(dictionary, resultDictionary);
// This requires a non-default IValidationStrategy
Assert.Contains(result.Model, context.ValidationState.Keys);
var entry = context.ValidationState[result.Model];
var strategy = Assert.IsType<ShortFormDictionaryValidationStrategy<int, ModelWithProperties>>(entry.Strategy);
Assert.Equal(
new KeyValuePair<string, int>[]
{
new KeyValuePair<string, int>("23", 23),
new KeyValuePair<string, int>("27", 27),
}.OrderBy(kvp => kvp.Key),
strategy.KeyMappings.OrderBy(kvp => kvp.Key));
}
[Theory]
@ -298,7 +313,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Equal(modelName, result.Key);
Assert.NotNull(result.ValidationNode);
var resultDictionary = Assert.IsAssignableFrom<SortedDictionary<string, string>>(result.Model);
Assert.Equal(expectedDictionary, resultDictionary);
@ -330,10 +344,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Empty(Assert.IsType<Dictionary<string, string>>(result.Model));
Assert.Equal("modelName", result.Key);
Assert.True(result.IsModelSet);
Assert.Same(result.ValidationNode.Model, result.Model);
Assert.Same(result.ValidationNode.Key, result.Key);
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
}
[Theory]
@ -403,7 +413,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
HttpContext = new DefaultHttpContext(),
MetadataProvider = new TestModelMetadataProvider(),
}
},
ValidationState = new ValidationStateDictionary(),
};
return modelBindingContext;
@ -462,7 +473,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
KeyValuePair<int, string> value;
if (values.TryGetValue(mbc.ModelName, out value))
{
return ModelBindingResult.SuccessAsync(mbc.ModelName, value, validationNode: null);
return ModelBindingResult.SuccessAsync(mbc.ModelName, value);
}
else
{
@ -488,6 +499,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
ValueProvider = valueProvider,
},
ValueProvider = valueProvider,
ValidationState = new ValidationStateDictionary(),
};
return bindingContext;

View File

@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Primitives;
using Moq;
using Xunit;
@ -36,8 +37,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.NotNull(result.ValidationNode);
Assert.True(result.ValidationNode.SuppressValidation);
var entry = bindingContext.ValidationState[result.Model];
Assert.True(entry.SuppressValidation);
Assert.Null(entry.Key);
Assert.Null(entry.Metadata);
var form = Assert.IsAssignableFrom<IFormCollection>(result.Model);
Assert.Equal(2, form.Count);
@ -124,7 +128,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelBinder = new FormCollectionModelBinder(),
MetadataProvider = metadataProvider,
HttpContext = httpContext,
}
},
ValidationState = new ValidationStateDictionary(),
};
return bindingContext;

View File

@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -17,6 +18,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class FormFileModelBinderTest
{
[Fact]
public async Task FormFileModelBinder_SuppressesValidation()
{
// Arrange
var formFiles = new FormFileCollection();
formFiles.Add(GetMockFormFile("file", "file1.txt"));
var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles));
var bindingContext = GetBindingContext(typeof(IEnumerable<IFormFile>), httpContext);
var binder = new FormFileModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
var entry = bindingContext.ValidationState[result.Model];
Assert.True(entry.SuppressValidation);
Assert.Null(entry.Key);
Assert.Null(entry.Metadata);
}
[Fact]
public async Task FormFileModelBinder_ExpectMultipleFiles_BindSuccessful()
{
@ -34,8 +58,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.NotNull(result.ValidationNode);
Assert.True(result.ValidationNode.SuppressValidation);
var entry = bindingContext.ValidationState[result.Model];
Assert.True(entry.SuppressValidation);
Assert.Null(entry.Key);
Assert.Null(entry.Metadata);
var files = Assert.IsAssignableFrom<IList<IFormFile>>(result.Model);
Assert.Equal(2, files.Count);
@ -197,7 +224,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelBinder = new FormFileModelBinder(),
MetadataProvider = metadataProvider,
HttpContext = httpContext,
}
},
ValidationState = new ValidationStateDictionary(),
};
return bindingContext;

View File

@ -32,7 +32,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.NotEqual(ModelBindingResult.NoResult, 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);
@ -54,7 +53,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
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.");
}
@ -99,19 +97,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.NotEqual(ModelBindingResult.NoResult, 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);
}
[Theory]
@ -126,11 +111,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
ModelBindingResult innerResult;
if (isSuccess)
{
innerResult = ModelBindingResult.Success(string.Empty, model, validationNode: null);
innerResult = ModelBindingResult.Success("somename.key", model);
}
else
{
innerResult = ModelBindingResult.Failed(string.Empty);
innerResult = ModelBindingResult.Failed("somename.key");
}
var innerBinder = new Mock<IModelBinder>();
@ -144,10 +129,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var bindingContext = GetBindingContext(new SimpleValueProvider(), innerBinder.Object);
var binder = new KeyValuePairModelBinder<int, string>();
var modelValidationNodeList = new List<ModelValidationNode>();
// Act
var result = await binder.TryBindStrongModel<int>(bindingContext, "key", modelValidationNodeList);
var result = await binder.TryBindStrongModel<int>(bindingContext, "key");
// Assert
Assert.Equal(innerResult, result);
@ -180,10 +164,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal(default(KeyValuePair<string, string>), Assert.IsType<KeyValuePair<string, string>>(result.Model));
Assert.Equal("modelName", result.Key);
Assert.True(result.IsModelSet);
Assert.Equal(result.ValidationNode.Model, result.Model);
Assert.Same(result.ValidationNode.Key, result.Key);
Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
}
[Theory]
@ -260,8 +240,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
if (mbc.ModelType == typeof(int))
{
var model = 42;
var validationNode = new ModelValidationNode(mbc.ModelName, mbc.ModelMetadata, model);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model);
}
return ModelBindingResult.NoResultAsync;
});
@ -278,8 +257,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
if (mbc.ModelType == typeof(string))
{
var model = "some-value";
var validationNode = new ModelValidationNode(mbc.ModelName, mbc.ModelMetadata, model);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model, validationNode);
return ModelBindingResult.SuccessAsync(mbc.ModelName, model);
}
return ModelBindingResult.NoResultAsync;
});

View File

@ -15,16 +15,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Arrange
var key = "someName";
var model = "some model";
var validationNode = new ModelValidationNode(key, null, model);
// Act
var result = ModelBindingResult.Success(key, model, validationNode);
var result = ModelBindingResult.Success(key, model);
// Assert
Assert.Same(key, result.Key);
Assert.True(result.IsModelSet);
Assert.Same(model, result.Model);
Assert.Same(validationNode, result.ValidationNode);
}
[Fact]
@ -33,16 +31,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Arrange
var key = "someName";
var model = "some model";
var validationNode = new ModelValidationNode(key, null, model);
// Act
var result = await ModelBindingResult.SuccessAsync(key, model, validationNode);
var result = await ModelBindingResult.SuccessAsync(key, model);
// Assert
Assert.Same(key, result.Key);
Assert.True(result.IsModelSet);
Assert.Same(model, result.Model);
Assert.Same(validationNode, result.ValidationNode);
}
[Fact]
@ -58,7 +54,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Same(key, result.Key);
Assert.False(result.IsModelSet);
Assert.Null(result.Model);
Assert.Null(result.ValidationNode);
}
[Fact]
@ -74,7 +69,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Same(key, result.Key);
Assert.False(result.IsModelSet);
Assert.Null(result.Model);
Assert.Null(result.ValidationNode);
}
[Fact]
@ -87,7 +81,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Null(result.Key);
Assert.False(result.IsModelSet);
Assert.Null(result.Model);
Assert.Null(result.ValidationNode);
}
[Fact]
@ -100,7 +93,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Null(result.Key);
Assert.False(result.IsModelSet);
Assert.Null(result.Model);
Assert.Null(result.ValidationNode);
}
}
}

View File

@ -786,13 +786,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
property => property,
property => ModelBindingResult.Failed(property.PropertyName));
var nameProperty = containerMetadata.Properties[nameof(model.Name)];
results[nameProperty] = ModelBindingResult.Success(string.Empty, "John Doe", validationNode: null);
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
results[nameProperty] = ModelBindingResult.Success(string.Empty, "John Doe");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -837,13 +836,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
property => property,
property => ModelBindingResult.Failed(property.PropertyName));
var nameProperty = containerMetadata.Properties[nameof(model.Name)];
results[nameProperty] = ModelBindingResult.Success(string.Empty, "John Doe", validationNode: null);
results[nameProperty] = ModelBindingResult.Success(string.Empty, "John Doe");
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -888,18 +886,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
property => property,
property => ModelBindingResult.Failed(property.PropertyName));
var propertyMetadata = containerMetadata.Properties[nameof(model.Name)];
results[propertyMetadata] = ModelBindingResult.Success("theModel.Name", "John Doe", validationNode: null);
results[propertyMetadata] = ModelBindingResult.Success("theModel.Name", "John Doe");
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
// case because the binding exists.
propertyMetadata = containerMetadata.Properties[nameof(model.Age)];
results[propertyMetadata] = ModelBindingResult.Success("theModel.Age", model: null, validationNode: null);
results[propertyMetadata] = ModelBindingResult.Success("theModel.Age", model: null);
var testableBinder = new TestableMutableObjectModelBinder();
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -929,10 +926,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
property => ModelBindingResult.Failed(property.PropertyName));
var testableBinder = new TestableMutableObjectModelBinder();
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -952,10 +948,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
property => ModelBindingResult.Failed(property.PropertyName));
var testableBinder = new TestableMutableObjectModelBinder();
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
var modelStateDictionary = bindingContext.ModelState;
@ -984,20 +979,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequired)];
results[propertyMetadata] = ModelBindingResult.Success(
key: "theModel." + nameof(Person.ValueTypeRequired),
model: null,
validationNode: null);
model: null);
// Make ValueTypeRequiredWithDefaultValue invalid
propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequiredWithDefaultValue)];
results[propertyMetadata] = ModelBindingResult.Success(
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue),
model: null,
validationNode: null);
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
model: null);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
Assert.False(modelStateDictionary.IsValid);
@ -1056,10 +1047,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
results[propertyMetadata] = ModelBindingResult.Failed(
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue));
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
Assert.True(modelState.IsValid);
@ -1084,24 +1073,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequired)];
results[propertyMetadata] = ModelBindingResult.Success(
key: "theModel." + nameof(Person.ValueTypeRequired),
model: 41,
validationNode: null);
model: 41);
// Make ValueTypeRequiredWithDefaultValue valid.
propertyMetadata = containerMetadata.Properties[nameof(Person.ValueTypeRequiredWithDefaultValue)];
results[propertyMetadata] = ModelBindingResult.Success(
key: "theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue),
model: 57,
validationNode: null);
model: 57);
// Also remind ProcessResults about PropertyWithDefaultValue -- as BindPropertiesAsync() would.
propertyMetadata = containerMetadata.Properties[nameof(Person.PropertyWithDefaultValue)];
results[propertyMetadata] = ModelBindingResult.Failed(
key: "theModel." + nameof(Person.PropertyWithDefaultValue));
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
Assert.True(modelStateDictionary.IsValid);
@ -1140,13 +1126,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty)];
results[propertyMetadata] = ModelBindingResult.Success(
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty),
model: "value",
validationNode: null);
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
model: "value");
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
Assert.False(modelStateDictionary.IsValid);
@ -1185,19 +1167,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ValueTypeProperty)];
results[propertyMetadata] = ModelBindingResult.Success(
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ValueTypeProperty),
model: 17,
validationNode: null);
model: 17);
// Make ReferenceTypeProperty not have a value.
propertyMetadata = containerMetadata
.Properties[nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty)];
results[propertyMetadata] = ModelBindingResult.Failed(
key: "theModel." + nameof(ModelWithBindRequiredAndRequiredAttribute.ReferenceTypeProperty));
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// Assert
Assert.False(modelStateDictionary.IsValid);
@ -1235,57 +1213,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var firstNameProperty = containerMetadata.Properties[nameof(model.FirstName)];
results[firstNameProperty] = ModelBindingResult.Success(
nameof(model.FirstName),
"John",
validationNode: null);
"John");
var lastNameProperty = containerMetadata.Properties[nameof(model.LastName)];
results[lastNameProperty] = ModelBindingResult.Success(
nameof(model.LastName),
"Doe",
validationNode: null);
var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model);
"Doe");
var testableBinder = new TestableMutableObjectModelBinder();
// Act
testableBinder.ProcessResults(bindingContext, results, modelValidationNode);
testableBinder.ProcessResults(bindingContext, results);
// 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(5, modelValidationNode.ChildNodes.Count);
Assert.Collection(modelValidationNode.ChildNodes,
child =>
{
Assert.Equal(nameof(model.DateOfBirth), child.Key);
Assert.Equal(null, child.Model);
},
child =>
{
Assert.Equal(nameof(model.DateOfDeath), child.Key);
Assert.Equal(null, child.Model);
},
child =>
{
Assert.Equal(nameof(model.FirstName), child.Key);
Assert.Equal("John", child.Model);
},
child =>
{
Assert.Equal(nameof(model.LastName), child.Key);
Assert.Equal("Doe", child.Model);
},
child =>
{
Assert.Equal(nameof(model.NonUpdateableProperty), child.Key);
Assert.Equal(null, child.Model);
});
}
[Fact]
@ -1420,8 +1364,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName];
var result = ModelBindingResult.Success(
propertyName,
new Simple { Name = "Hanna" },
validationNode: null);
new Simple { Name = "Hanna" });
var testableBinder = new TestableMutableObjectModelBinder();
@ -1496,7 +1439,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(type, model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[propertyName];
var result = ModelBindingResult.Success(propertyName, collection, validationNode: null);
var result = ModelBindingResult.Success(propertyName, collection);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
@ -1519,7 +1462,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
var result = ModelBindingResult.Success("foo", new DateTime(2001, 1, 1), validationNode: null);
var result = ModelBindingResult.Success("foo", new DateTime(2001, 1, 1));
var testableBinder = new TestableMutableObjectModelBinder();
// Act
@ -1546,7 +1489,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfDeath)];
var result = ModelBindingResult.Success("foo", new DateTime(1800, 1, 1), validationNode: null);
var result = ModelBindingResult.Success("foo", new DateTime(1800, 1, 1));
var testableBinder = new TestableMutableObjectModelBinder();
// Act
@ -1571,7 +1514,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(Person), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.DateOfBirth)];
var result = ModelBindingResult.Success("foo.DateOfBirth", model: null, validationNode: null);
var result = ModelBindingResult.Success("foo.DateOfBirth", model: null);
var testableBinder = new TestableMutableObjectModelBinder();
// Act
@ -1599,7 +1542,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(ModelWhosePropertySetterThrows), model);
var propertyMetadata = bindingContext.ModelMetadata.Properties[nameof(model.NameNoAttribute)];
var result = ModelBindingResult.Success("foo.NameNoAttribute", model: null, validationNode: null);
var result = ModelBindingResult.Success("foo.NameNoAttribute", model: null);
var testableBinder = new TestableMutableObjectModelBinder();
// Act

View File

@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.DependencyInjection;
using Xunit;
@ -29,9 +30,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.NotNull(result.Model);
Assert.Equal("modelName", result.Key);
Assert.NotNull(result.ValidationNode);
Assert.Equal("modelName", result.ValidationNode.Key);
Assert.True(result.ValidationNode.SuppressValidation);
var entry = modelBindingContext.ValidationState[result.Model];
Assert.True(entry.SuppressValidation);
Assert.Null(entry.Key);
Assert.Null(entry.Metadata);
}
[Fact]
@ -95,6 +97,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
},
BinderModelName = modelMetadata.BinderModelName,
BindingSource = modelMetadata.BindingSource,
ValidationState = new ValidationStateDictionary(),
};
return bindingContext;

View File

@ -103,7 +103,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.False(result.IsModelSet);
Assert.Null(result.Model);
Assert.Null(result.ValidationNode);
var error = Assert.Single(bindingContext.ModelState["theModelName"].Errors);
Assert.Equal(error.ErrorMessage, "The value '' is invalid.", StringComparer.Ordinal);

View File

@ -0,0 +1,98 @@
// 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.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class DefaultCollectionValidationStrategyTest
{
[Fact]
public void EnumerateElements()
{
// Arrange
var model = new List<int>() { 2, 3, 5 };
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(List<int>));
var strategy = DefaultCollectionValidationStrategy.Instance;
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[0]", e.Key);
Assert.Equal(2, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[1]", e.Key);
Assert.Equal(3, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[2]", e.Key);
Assert.Equal(5, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_Dictionary()
{
// Arrange
var model = new Dictionary<int, string>()
{
{ 2, "two" },
{ 3, "three" },
{ 5, "five" },
};
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(List<int>));
var strategy = DefaultCollectionValidationStrategy.Instance;
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[0]", e.Key);
Assert.Equal(new KeyValuePair<int, string>(2, "two"), e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[1]", e.Key);
Assert.Equal(new KeyValuePair<int, string>(3, "three"), e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[2]", e.Key);
Assert.Equal(new KeyValuePair<int, string>(5, "five"), e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
private List<ValidationEntry> BufferEntries(IEnumerator<ValidationEntry> enumerator)
{
var entries = new List<ValidationEntry>();
while (enumerator.MoveNext())
{
entries.Add(enumerator.Current);
}
return entries;
}
}
}

View File

@ -0,0 +1,72 @@
// 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.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class DefaultComplexObjectValidationStrategyTest
{
[Fact]
public void EnumerateElements()
{
// Arrange
var model = new Person()
{
Age = 23,
Id = 1,
Name = "Joey",
};
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(Person));
var strategy = DefaultComplexObjectValidationStrategy.Instance;
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix.Age", e.Key);
Assert.Equal(23, e.Model);
Assert.Same(metadata.Properties["Age"], e.Metadata);
},
e =>
{
Assert.Equal("prefix.Id", e.Key);
Assert.Equal(1, e.Model);
Assert.Same(metadata.Properties["Id"], e.Metadata);
},
e =>
{
Assert.Equal("prefix.Name", e.Key);
Assert.Equal("Joey", e.Model);
Assert.Same(metadata.Properties["Name"], e.Metadata);
});
}
private List<ValidationEntry> BufferEntries(IEnumerator<ValidationEntry> enumerator)
{
var entries = new List<ValidationEntry>();
while (enumerator.MoveNext())
{
entries.Add(enumerator.Current);
}
return entries;
}
private class Person
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
}
}
}

View File

@ -0,0 +1,159 @@
// 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.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class ExplicitIndexCollectionValidationStrategyTest
{
[Fact]
public void EnumerateElements_List()
{
// Arrange
var model = new List<int>() { 2, 3, 5 };
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(List<int>));
var strategy = new ExplicitIndexCollectionValidationStrategy(new string[] { "zero", "one", "two" });
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[one]", e.Key);
Assert.Equal(3, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[two]", e.Key);
Assert.Equal(5, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[zero]", e.Key);
Assert.Equal(2, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_Dictionary()
{
// Arrange
var model = new Dictionary<int, string>()
{
{ 2, "two" },
{ 3, "three" },
{ 5, "five" },
};
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(List<int>));
var strategy = new ExplicitIndexCollectionValidationStrategy(new string[] { "zero", "one", "two" });
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[one]", e.Key);
Assert.Equal(new KeyValuePair<int, string>(3, "three"), e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[two]", e.Key);
Assert.Equal(new KeyValuePair<int, string>(5, "five"), e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[zero]", e.Key);
Assert.Equal(new KeyValuePair<int, string>(2, "two"), e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_RunOutOfIndices()
{
// Arrange
var model = new List<int>() { 2, 3, 5 };
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(List<int>));
var strategy = new ExplicitIndexCollectionValidationStrategy(new string[] { "zero", "one", });
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[one]", e.Key);
Assert.Equal(3, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[zero]", e.Key);
Assert.Equal(2, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_RunOutOfElements()
{
// Arrange
var model = new List<int>() { 2, 3, };
var metadata = TestModelMetadataProvider.CreateDefaultProvider().GetMetadataForType(typeof(List<int>));
var strategy = new ExplicitIndexCollectionValidationStrategy(new string[] { "zero", "one", "two" });
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[one]", e.Key);
Assert.Equal(3, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[zero]", e.Key);
Assert.Equal(2, e.Model);
Assert.Same(metadata.ElementMetadata, e.Metadata);
});
}
private List<ValidationEntry> BufferEntries(IEnumerator<ValidationEntry> enumerator)
{
var entries = new List<ValidationEntry>();
while (enumerator.MoveNext())
{
entries.Add(enumerator.Current);
}
return entries;
}
}
}

View File

@ -0,0 +1,154 @@
// 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.Linq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class ShortFormDictionaryValidationStrategyTest
{
[Fact]
public void EnumerateElements()
{
// Arrange
var model = new Dictionary<int, string>()
{
{ 2, "two" },
{ 3, "three" },
{ 5, "five" },
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var metadata = metadataProvider.GetMetadataForType(typeof(List<int>));
var valueMetadata = metadataProvider.GetMetadataForType(typeof(string));
var strategy = new ShortFormDictionaryValidationStrategy<int, string>(new Dictionary<string, int>()
{
{ "2", 2 },
{ "3", 3 },
{ "5", 5 },
},
valueMetadata);
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[2]", e.Key);
Assert.Equal("two", e.Model);
Assert.Same(valueMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[3]", e.Key);
Assert.Equal("three", e.Model);
Assert.Same(valueMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[5]", e.Key);
Assert.Equal("five", e.Model);
Assert.Same(valueMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_RunOutOfIndices()
{
// Arrange
var model = new Dictionary<int, string>()
{
{ 2, "two" },
{ 3, "three" },
{ 5, "five" },
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var metadata = metadataProvider.GetMetadataForType(typeof(List<int>));
var valueMetadata = metadataProvider.GetMetadataForType(typeof(string));
var strategy = new ShortFormDictionaryValidationStrategy<int, string>(new Dictionary<string, int>()
{
{ "2", 2 },
{ "3", 3 },
},
valueMetadata);
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[2]", e.Key);
Assert.Equal("two", e.Model);
Assert.Same(valueMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[3]", e.Key);
Assert.Equal("three", e.Model);
Assert.Same(valueMetadata, e.Metadata);
});
}
[Fact]
public void EnumerateElements_RunOutOfElements()
{
// Arrange
var model = new Dictionary<int, string>()
{
{ 2, "two" },
{ 3, "three" },
};
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var metadata = metadataProvider.GetMetadataForType(typeof(List<int>));
var valueMetadata = metadataProvider.GetMetadataForType(typeof(string));
var strategy = new ShortFormDictionaryValidationStrategy<int, string>(new Dictionary<string, int>()
{
{ "2", 2 },
{ "3", 3 },
{ "5", 5 },
},
valueMetadata);
// Act
var enumerator = strategy.GetChildren(metadata, "prefix", model);
// Assert
Assert.Collection(
BufferEntries(enumerator).OrderBy(e => e.Key),
e =>
{
Assert.Equal("prefix[2]", e.Key);
Assert.Equal("two", e.Model);
Assert.Same(valueMetadata, e.Metadata);
},
e =>
{
Assert.Equal("prefix[3]", e.Key);
Assert.Equal("three", e.Model);
Assert.Same(valueMetadata, e.Metadata);
});
}
private List<ValidationEntry> BufferEntries(IEnumerator<ValidationEntry> enumerator)
{
var entries = new List<ValidationEntry>();
while (enumerator.MoveNext())
{
entries.Add(enumerator.Current);
}
return entries;
}
}
}

View File

@ -262,11 +262,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
private static ModelValidationContext CreateValidationContext(ModelExplorer modelExplorer)
{
return new ModelValidationContext(
bindingSource: null,
modelState: null,
validatorProvider: null,
modelExplorer: modelExplorer);
return new ModelValidationContext()
{
Container = modelExplorer.Container,
Metadata = modelExplorer.Metadata,
Model = modelExplorer.Model,
};
}
private class DerivedRequiredAttribute : RequiredAttribute

View File

@ -107,13 +107,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Read-only collection should not be updated.
Assert.Empty(boundModel.Address);
// ModelState (data is valid but is not copied into Address).
Assert.True(modelState.IsValid);
// ModelState (data is can't be validated).
Assert.False(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState);
Assert.Equal("SomeStreet", state.RawValue);
Assert.Equal("SomeStreet", state.AttemptedValue);
}
@ -286,13 +286,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Read-only collection should not be updated.
Assert.Empty(boundModel.Address);
// ModelState (data is valid but is not copied into Address).
Assert.True(modelState.IsValid);
// ModelState (data cannot be validated).
Assert.False(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address[0].Street", entry.Key);
var state = entry.Value;
Assert.NotNull(state);
Assert.Equal(ModelValidationState.Valid, state.ValidationState);
Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState);
Assert.Equal("SomeStreet", state.AttemptedValue);
Assert.Equal("SomeStreet", state.RawValue);
}

View File

@ -44,7 +44,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState (not set unless inner binder sets it)
Assert.True(modelState.IsValid);
Assert.Empty(modelState);
var entry = modelState[string.Empty];
Assert.Null(entry.AttemptedValue);
Assert.Null(entry.RawValue);
Assert.Empty(entry.Errors);
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
}
[Fact]
@ -294,15 +298,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
new string[] { address.Street },
address.Street);
var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
address)
{
ValidateAllProperties = true
};
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, address, validationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, address);
}
}
@ -316,11 +312,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
new string[] { model },
model);
var modelValidationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, modelValidationNode);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
}
@ -328,7 +320,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model: null, validationNode: null);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model: null);
}
}

View File

@ -630,12 +630,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
return _data.GetEnumerator();
}
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
{
throw new NotImplementedException();
return _data.GetEnumerator();
}
bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
@ -650,7 +650,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
bool IDictionary<string, string>.TryGetValue(string key, out string value)
{
throw new NotImplementedException();
return _data.TryGetValue(key, out value);
}
}
@ -735,12 +735,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
return _data.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
throw new NotImplementedException();
return _data.GetEnumerator();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
@ -755,7 +755,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)
{
throw new NotImplementedException();
return _data.TryGetValue(key, out value);
}
}
}

View File

@ -161,7 +161,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
return ModelBindingResult.NoResultAsync;
}
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, new Address(), validationNode: null);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, new Address());
}
}

View File

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Xunit;
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
@ -27,8 +28,11 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
Assert.NotEqual(ModelBindingResult.NoResult, result);
Assert.True(result.IsModelSet);
Assert.Same(expectedModel, result.Model);
Assert.NotNull(result.ValidationNode);
Assert.True(result.ValidationNode.SuppressValidation);
var entry = bindingContext.ValidationState[result.Model];
Assert.True(entry.SuppressValidation);
Assert.Null(entry.Key);
Assert.Null(entry.Metadata);
}
[Theory]
@ -59,7 +63,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
HttpContext = new DefaultHttpContext(),
MetadataProvider = metadataProvider,
}
},
ValidationState = new ValidationStateDictionary(),
};
bindingContext.OperationBindingContext.HttpContext.Request.Method = "GET";

View File

@ -75,9 +75,7 @@ namespace ModelBindingWebSite.Controllers
OrderStatus model;
if (Enum.TryParse<OrderStatus>("Status" + request.Query["status"], out model))
{
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model);
return ModelBindingResult.SuccessAsync("status", model, validationNode);
return ModelBindingResult.SuccessAsync("status", model);
}
return ModelBindingResult.FailedAsync("status");
@ -105,9 +103,7 @@ namespace ModelBindingWebSite.Controllers
var value = bindingContext.ValueProvider.GetValue(key);
model.ProductId = value.ConvertTo<int>();
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value);
return ModelBindingResult.SuccessAsync(key, model, validationNode);
return ModelBindingResult.SuccessAsync(key, model);
}
return ModelBindingResult.NoResultAsync;

View File

@ -27,7 +27,7 @@ namespace ModelBindingWebSite
if (!IsSimpleType(bindingContext.ModelType))
{
model = Activator.CreateInstance(bindingContext.ModelType);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model, validationNode: null);
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, model);
}
return ModelBindingResult.FailedAsync(bindingContext.ModelName);