Remove IExcludeTypeFilter

- Removes IExcludeTypeFilter
- Replaced with a property 'ValidateChildren' on ModelMetadata
- Teach ValidationVisitor to respect 'ValidateChildren' for enumerable
  types.
This commit is contained in:
Ryan Nowak 2015-12-08 18:22:10 -08:00
parent 774ee05508
commit 96de1dbe4b
37 changed files with 514 additions and 534 deletions

View File

@ -289,6 +289,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary> /// </summary>
public abstract string TemplateHint { get; } public abstract string TemplateHint { get; }
/// <summary>
/// Gets a value that indicates whether properties or elements of the model should be validated.
/// </summary>
public abstract bool ValidateChildren { get; }
/// <summary> /// <summary>
/// Gets a collection of metadata items for validators. /// Gets a collection of metadata items for validators.
/// </summary> /// </summary>

View File

@ -134,12 +134,7 @@ namespace Microsoft.Extensions.DependencyInjection
var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value; var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value;
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders); return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
})); }));
services.TryAdd(ServiceDescriptor.Singleton<IObjectModelValidator>(serviceProvider => services.TryAddSingleton<IObjectModelValidator, DefaultObjectValidator>();
{
var options = serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value;
var modelMetadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();
return new DefaultObjectValidator(options.ValidationExcludeFilters, modelMetadataProvider);
}));
// //
// Random Infrastructure // Random Infrastructure

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Threading;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Formatters; using Microsoft.AspNet.Mvc.Formatters;
@ -63,13 +64,11 @@ namespace Microsoft.AspNet.Mvc.Internal
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());
// Add types to be excluded from Validation // Add types to be excluded from Validation
options.ValidationExcludeFilters.Add(new SimpleTypesExcludeFilter()); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(Type)));
options.ValidationExcludeFilters.Add(typeof(Type)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(Uri)));
options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(CancellationToken)));
// Any 'known' types that we bind should be marked as excluded from validation. options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(IFormFile)));
options.ValidationExcludeFilters.Add(typeof(System.Threading.CancellationToken)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(IFormCollection)));
options.ValidationExcludeFilters.Add(typeof(IFormFile));
options.ValidationExcludeFilters.Add(typeof(IFormCollection));
} }
} }
} }

View File

@ -29,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
private bool? _isReadOnly; private bool? _isReadOnly;
private bool? _isRequired; private bool? _isRequired;
private ModelPropertyCollection _properties; private ModelPropertyCollection _properties;
private bool? _validateChildren;
private ReadOnlyCollection<object> _validatorMetadata; private ReadOnlyCollection<object> _validatorMetadata;
/// <summary> /// <summary>
@ -491,6 +492,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
} }
} }
/// <inheritdoc />
public override bool ValidateChildren
{
get
{
if (!_validateChildren.HasValue)
{
if (ValidationMetadata.ValidateChildren.HasValue)
{
_validateChildren = ValidationMetadata.ValidateChildren.Value;
}
else if (IsComplexType || IsEnumerableType)
{
_validateChildren = true;
}
else
{
_validateChildren = false;
}
}
return _validateChildren.Value;
}
}
/// <inheritdoc /> /// <inheritdoc />
public override IReadOnlyList<object> ValidatorMetadata public override IReadOnlyList<object> ValidatorMetadata
{ {

View File

@ -18,6 +18,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// </summary> /// </summary>
public bool? IsRequired { get; set; } public bool? IsRequired { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether children of the model should be validated. If <c>null</c>
/// then <see cref="ModelMetadata.ValidateChildren"/> will be <c>true</c> if either of
/// <see cref="ModelMetadata.IsComplexType"/> or <see cref="ModelMetadata.IsEnumerableType"/> is <c>true</c>;
/// <c>false</c> otherwise.
/// </summary>
public bool? ValidateChildren { get; set; }
/// <summary> /// <summary>
/// Gets a list of metadata items for validators. /// Gets a list of metadata items for validators.
/// </summary> /// </summary>

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{ {
@ -11,31 +10,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
/// </summary> /// </summary>
public class DefaultObjectValidator : IObjectModelValidator public class DefaultObjectValidator : IObjectModelValidator
{ {
private readonly IList<IExcludeTypeValidationFilter> _excludeFilters;
private readonly IModelMetadataProvider _modelMetadataProvider; private readonly IModelMetadataProvider _modelMetadataProvider;
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="DefaultObjectValidator"/>. /// Initializes a new instance of <see cref="DefaultObjectValidator"/>.
/// </summary> /// </summary>
/// <param name="excludeFilters"><see cref="IExcludeTypeValidationFilter"/>s that determine
/// types to exclude from validation.</param>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
public DefaultObjectValidator( public DefaultObjectValidator(
IList<IExcludeTypeValidationFilter> excludeFilters,
IModelMetadataProvider modelMetadataProvider) IModelMetadataProvider modelMetadataProvider)
{ {
if (excludeFilters == null)
{
throw new ArgumentNullException(nameof(excludeFilters));
}
if (modelMetadataProvider == null) if (modelMetadataProvider == null)
{ {
throw new ArgumentNullException(nameof(modelMetadataProvider)); throw new ArgumentNullException(nameof(modelMetadataProvider));
} }
_modelMetadataProvider = modelMetadataProvider; _modelMetadataProvider = modelMetadataProvider;
_excludeFilters = excludeFilters;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -59,7 +48,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
var visitor = new ValidationVisitor( var visitor = new ValidationVisitor(
actionContext, actionContext,
validatorProvider, validatorProvider,
_excludeFilters,
validationState); validationState);
var metadata = model == null ? null : _modelMetadataProvider.GetMetadataForType(model.GetType()); var metadata = model == null ? null : _modelMetadataProvider.GetMetadataForType(model.GetType());

View File

@ -1,45 +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;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Provides an implementation of <see cref="IExcludeTypeValidationFilter"/> which can filter
/// based on a type.
/// </summary>
public class DefaultTypeBasedExcludeFilter : IExcludeTypeValidationFilter
{
/// <summary>
/// Creates a new instance of <see cref="DefaultTypeBasedExcludeFilter"/>.
/// </summary>
/// <param name="type">The type which needs to be excluded.</param>
public DefaultTypeBasedExcludeFilter(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
ExcludedType = type;
}
/// <summary>
/// Gets the type which is excluded from validation.
/// </summary>
public Type ExcludedType { get; }
/// <inheritdoc />
public bool IsTypeExcluded(Type propertyType)
{
if (propertyType == null)
{
throw new ArgumentNullException(nameof(propertyType));
}
return ExcludedType.GetTypeInfo().IsAssignableFrom(propertyType.GetTypeInfo());
}
}
}

View File

@ -1,60 +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;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Provides an implementation of <see cref="IExcludeTypeValidationFilter"/> which can filter
/// based on a type full name.
/// </summary>
public class DefaultTypeNameBasedExcludeFilter : IExcludeTypeValidationFilter
{
/// <summary>
/// Creates a new instance of <see cref="DefaultTypeNameBasedExcludeFilter"/>
/// </summary>
/// <param name="typeFullName">Fully qualified name of the type which needs to be excluded.</param>
public DefaultTypeNameBasedExcludeFilter(string typeFullName)
{
if (typeFullName == null)
{
throw new ArgumentNullException(nameof(typeFullName));
}
ExcludedTypeName = typeFullName;
}
/// <summary>
/// Gets the type full name which is excluded from validation.
/// </summary>
public string ExcludedTypeName { get; }
/// <inheritdoc />
public bool IsTypeExcluded(Type propertyType)
{
if (propertyType == null)
{
throw new ArgumentNullException(nameof(propertyType));
}
return CheckIfTypeNameMatches(propertyType);
}
private bool CheckIfTypeNameMatches(Type type)
{
if (type == null)
{
return false;
}
if (string.Equals(type.FullName, ExcludedTypeName, StringComparison.Ordinal))
{
return true;
}
return CheckIfTypeNameMatches(type.GetTypeInfo().BaseType);
}
}
}

View File

@ -1,43 +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;
using System.Collections.ObjectModel;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class ExcludeTypeValidationFilterCollection : Collection<IExcludeTypeValidationFilter>
{
/// <summary>
/// Adds an <see cref="IExcludeTypeValidationFilter"/> that excludes the properties of
/// the <see cref="Type"/> specified and its derived types from validaton.
/// </summary>
/// <param name="type"><see cref="Type"/> which should be excluded from validation.</param>
public void Add(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
var typeBasedExcludeFilter = new DefaultTypeBasedExcludeFilter(type);
Add(typeBasedExcludeFilter);
}
/// <summary>
/// Adds an <see cref="IExcludeTypeValidationFilter"/> that excludes the properties of
/// the <see cref="Type"/> specified and its derived types from validaton.
/// </summary>
/// <param name="typeFullName">Full name of the type which should be excluded from validation.</param>
public void Add(string typeFullName)
{
if (typeFullName == null)
{
throw new ArgumentNullException(nameof(typeFullName));
}
var filter = new DefaultTypeNameBasedExcludeFilter(typeFullName);
Add(filter);
}
}
}

View File

@ -1,20 +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;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Provides an interface which is used to determine if <see cref="Type"/>s are excluded from model validation.
/// </summary>
public interface IExcludeTypeValidationFilter
{
/// <summary>
/// Determines if the given type is excluded from validation.
/// </summary>
/// <param name="type">The <see cref="Type"/> for which the check is to be performed.</param>
/// <returns>True if the type is to be excluded. False otherwise.</returns>
bool IsTypeExcluded(Type type);
}
}

View File

@ -1,64 +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;
using System.Collections.Generic;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// Identifies the simple types that the default model binding validation will exclude.
/// </summary>
public class SimpleTypesExcludeFilter : IExcludeTypeValidationFilter
{
/// <summary>
/// Returns true if the given type will be excluded from the default model validation.
/// </summary>
public bool IsTypeExcluded(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
Type[] actualTypes;
if (type.GetTypeInfo().IsGenericType &&
type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
actualTypes = type.GenericTypeArguments;
}
else
{
actualTypes = new Type[] { type };
}
foreach (var actualType in actualTypes)
{
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
if (!IsSimpleType(underlyingType))
{
return false;
}
}
return true;
}
/// <summary>
/// Returns true if the given type is the underlying types that <see cref="IsTypeExcluded"/> will exclude.
/// </summary>
protected virtual bool IsSimpleType(Type type)
{
return type.GetTypeInfo().IsPrimitive ||
type.Equals(typeof(decimal)) ||
type.Equals(typeof(string)) ||
type.Equals(typeof(DateTime)) ||
type.Equals(typeof(Guid)) ||
type.Equals(typeof(DateTimeOffset)) ||
type.Equals(typeof(TimeSpan)) ||
type.Equals(typeof(Uri));
}
}
}

View File

@ -0,0 +1,109 @@
// 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.Diagnostics;
using System.Reflection;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// An <see cref="IValidationMetadataProvider"/> which configures <see cref="ModelMetadata.ValidateChildren"/> to
/// <c>false</c> for matching types.
/// </summary>
public class ValidationExcludeFilter : IValidationMetadataProvider
{
/// <summary>
/// Creates a new <see cref="ValidationExcludeFilter"/> for the given <paramref name="type"/>.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/>. This <see cref="Type"/> and all assignable values will have
/// <see cref="ModelMetadata.ValidateChildren"/> set to <c>false</c>.
/// </param>
public ValidationExcludeFilter(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
Type = type;
}
/// <summary>
/// Creates a new <see cref="ValidationExcludeFilter"/> for the given <paramref name="fullTypeName"/>.
/// </summary>
/// <param name="fullTypeName">
/// The type full name. This type and all of its subclasses will have
/// <see cref="ModelMetadata.ValidateChildren"/> set to <c>false</c>.
/// </param>
public ValidationExcludeFilter(string fullTypeName)
{
if (fullTypeName == null)
{
throw new ArgumentNullException(nameof(fullTypeName));
}
FullTypeName = fullTypeName;
}
/// <summary>
/// Gets the <see cref="Type"/> for which to suppress validation of children.
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the full name of a type for which to suppress validation of children.
/// </summary>
public string FullTypeName { get; }
/// <inheritdoc />
public void GetValidationMetadata(ValidationMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (Type != null)
{
if (Type.GetTypeInfo().IsAssignableFrom(context.Key.ModelType.GetTypeInfo()))
{
context.ValidationMetadata.ValidateChildren = false;
}
return;
}
if (FullTypeName != null)
{
if (IsMatchingName(context.Key.ModelType))
{
context.ValidationMetadata.ValidateChildren = false;
}
return;
}
Debug.Fail("We shouldn't get here.");
}
private bool IsMatchingName(Type type)
{
Debug.Assert(FullTypeName != null);
if (type == null)
{
return false;
}
if (string.Equals(type.FullName, FullTypeName, StringComparison.Ordinal))
{
return true;
}
return IsMatchingName(type.GetTypeInfo().BaseType);
}
}
}

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
public class ValidationVisitor public class ValidationVisitor
{ {
private readonly IModelValidatorProvider _validatorProvider; private readonly IModelValidatorProvider _validatorProvider;
private readonly IList<IExcludeTypeValidationFilter> _excludeFilters;
private readonly ActionContext _actionContext; private readonly ActionContext _actionContext;
private readonly ModelStateDictionary _modelState; private readonly ModelStateDictionary _modelState;
private readonly ValidationStateDictionary _validationState; private readonly ValidationStateDictionary _validationState;
@ -33,12 +32,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
/// </summary> /// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param> /// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param>
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param> /// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/>.</param>
/// <param name="excludeFilters">The list of <see cref="IExcludeTypeValidationFilter"/>.</param>
/// <param name="validationState">The <see cref="ValidationStateDictionary"/>.</param> /// <param name="validationState">The <see cref="ValidationStateDictionary"/>.</param>
public ValidationVisitor( public ValidationVisitor(
ActionContext actionContext, ActionContext actionContext,
IModelValidatorProvider validatorProvider, IModelValidatorProvider validatorProvider,
IList<IExcludeTypeValidationFilter> excludeFilters,
ValidationStateDictionary validationState) ValidationStateDictionary validationState)
{ {
if (actionContext == null) if (actionContext == null)
@ -51,14 +48,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
throw new ArgumentNullException(nameof(validatorProvider)); throw new ArgumentNullException(nameof(validatorProvider));
} }
if (excludeFilters == null)
{
throw new ArgumentNullException(nameof(excludeFilters));
}
_actionContext = actionContext; _actionContext = actionContext;
_validatorProvider = validatorProvider; _validatorProvider = validatorProvider;
_excludeFilters = excludeFilters;
_validationState = validationState; _validationState = validationState;
_modelState = actionContext.ModelState; _modelState = actionContext.ModelState;
@ -191,11 +182,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{ {
var isValid = true; var isValid = true;
if (_model != null) if (_model != null && _metadata.ValidateChildren)
{ {
var strategy = _strategy ?? DefaultCollectionValidationStrategy.Instance; var strategy = _strategy ?? DefaultCollectionValidationStrategy.Instance;
isValid = VisitChildren(strategy); isValid = VisitChildren(strategy);
} }
else if (_model != null)
{
// Suppress validation for the entries matching this prefix. This will temporarily set
// the current node to 'skipped' but we're going to visit it right away, so subsequent
// code will set it to 'valid' or 'invalid'
SuppressValidation(_key);
}
// Double-checking HasReachedMaxErrors just in case this model has no elements. // Double-checking HasReachedMaxErrors just in case this model has no elements.
if (isValid && !_modelState.HasReachedMaxErrors) if (isValid && !_modelState.HasReachedMaxErrors)
@ -210,14 +208,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{ {
var isValid = true; var isValid = true;
if (_model != null && ShouldValidateProperties(_metadata)) if (_model != null && _metadata.ValidateChildren)
{ {
var strategy = _strategy ?? DefaultComplexObjectValidationStrategy.Instance; var strategy = _strategy ?? DefaultComplexObjectValidationStrategy.Instance;
isValid = VisitChildren(strategy); isValid = VisitChildren(strategy);
} }
else if (_model != null) else if (_model != null)
{ {
// Suppress validation for the prefix, but we still want to validate the object. // Suppress validation for the entries matching this prefix. This will temporarily set
// the current node to 'skipped' but we're going to visit it right away, so subsequent
// code will set it to 'valid' or 'invalid'
SuppressValidation(_key); SuppressValidation(_key);
} }
@ -286,20 +286,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
} }
} }
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) private ValidationStateEntry GetValidationEntry(object model)
{ {
if (model == null || _validationState == null) if (model == null || _validationState == null)

View File

@ -31,7 +31,6 @@ namespace Microsoft.AspNet.Mvc
ModelBindingMessageProvider = new ModelBindingMessageProvider(); ModelBindingMessageProvider = new ModelBindingMessageProvider();
ModelMetadataDetailsProviders = new List<IMetadataDetailsProvider>(); ModelMetadataDetailsProviders = new List<IMetadataDetailsProvider>();
ModelValidatorProviders = new List<IModelValidatorProvider>(); ModelValidatorProviders = new List<IModelValidatorProvider>();
ValidationExcludeFilters = new ExcludeTypeValidationFilterCollection();
ValueProviderFactories = new List<IValueProviderFactory>(); ValueProviderFactories = new List<IValueProviderFactory>();
} }
@ -125,11 +124,6 @@ namespace Microsoft.AspNet.Mvc
/// </summary> /// </summary>
public bool RespectBrowserAcceptHeader { get; set; } public bool RespectBrowserAcceptHeader { get; set; }
/// <summary>
/// Gets a collection of <see cref="IExcludeTypeValidationFilter"/>s that are used by this application.
/// </summary>
public ExcludeTypeValidationFilterCollection ValidationExcludeFilters { get; }
/// <summary> /// <summary>
/// Gets a list of <see cref="IValueProviderFactory"/> used by this application. /// Gets a list of <see cref="IValueProviderFactory"/> used by this application.
/// </summary> /// </summary>

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.OptionsModel;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -30,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.Formatters.Json.Internal
options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json")); options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("application/json"));
options.ValidationExcludeFilters.Add(typeof(JToken)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(JToken)));
} }
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.OptionsModel;
namespace Microsoft.AspNet.Mvc.Formatters.Xml.Internal namespace Microsoft.AspNet.Mvc.Formatters.Xml.Internal
@ -31,8 +32,8 @@ namespace Microsoft.AspNet.Mvc.Formatters.Xml.Internal
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter()); options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.Linq.XObject"); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter("System.Xml.Linq.XObject"));
options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.XmlNode"); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter("System.Xml.XmlNode"));
} }
} }
} }

View File

@ -3,6 +3,7 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Formatting; using System.Net.Http.Formatting;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.OptionsModel;
@ -32,8 +33,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
// Add a formatter to write out an HttpResponseMessage to the response // Add a formatter to write out an HttpResponseMessage to the response
options.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter()); options.OutputFormatters.Insert(0, new HttpResponseMessageOutputFormatter());
options.ValidationExcludeFilters.Add(typeof(HttpRequestMessage)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(HttpRequestMessage)));
options.ValidationExcludeFilters.Add(typeof(HttpResponseMessage)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(HttpResponseMessage)));
} }
public void Configure(WebApiCompatShimOptions options) public void Configure(WebApiCompatShimOptions options)

View File

@ -568,6 +568,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
} }
public override bool ValidateChildren
{
get
{
throw new NotImplementedException();
}
}
public override IReadOnlyList<object> ValidatorMetadata public override IReadOnlyList<object> ValidatorMetadata
{ {
get get

View File

@ -2112,7 +2112,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
new IInputFormatter[0], new IInputFormatter[0],
new DefaultControllerActionArgumentBinder( new DefaultControllerActionArgumentBinder(
metadataProvider, metadataProvider,
new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider)), new DefaultObjectValidator(metadataProvider)),
new IModelBinder[] { binder.Object }, new IModelBinder[] { binder.Object },
new IModelValidatorProvider[0], new IModelValidatorProvider[0],
new IValueProviderFactory[0], new IValueProviderFactory[0],

View File

@ -251,7 +251,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
services.Setup(s => s.GetService(typeof(IModelMetadataProvider))) services.Setup(s => s.GetService(typeof(IModelMetadataProvider)))
.Returns(metadataProvider); .Returns(metadataProvider);
services.Setup(s => s.GetService(typeof(IObjectModelValidator))) services.Setup(s => s.GetService(typeof(IObjectModelValidator)))
.Returns(new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider)); .Returns(new DefaultObjectValidator(metadataProvider));
return services.Object; return services.Object;
} }

View File

@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Xml;
using Moq; using Moq;
using Xunit; using Xunit;
@ -605,6 +606,103 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.False(isReadOnly); Assert.False(isReadOnly);
} }
[Theory]
[InlineData(typeof(int))] // Primitive
[InlineData(typeof(int?))] // Nullable
[InlineData(typeof(Guid))] // TypeConverter
[InlineData(typeof(Guid?))] // Nullable + TypeConverter
[InlineData(typeof(string))]
public void ValidateChildren_SimpleTypes(Type modelType)
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(modelType);
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var validateChildren = metadata.ValidateChildren;
// Assert
Assert.False(validateChildren);
}
[Theory]
[InlineData(typeof(int[]))]
[InlineData(typeof(List<decimal>))]
[InlineData(typeof(IEnumerable))]
[InlineData(typeof(Dictionary<string, string>))]
[InlineData(typeof(KeyValuePair<string, string>))]
[InlineData(typeof(KeyValuePair<string, string>?))]
[InlineData(typeof(TypeWithProperties))]
[InlineData(typeof(List<TypeWithProperties>))]
public void ValidateChildren_ComplexAndEnumerableTypes(Type modelType)
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var validateChildren = metadata.ValidateChildren;
// Assert
Assert.True(validateChildren);
}
[Fact]
public void ValidateChildren_OverrideTrue()
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(int));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
cache.ValidationMetadata = new ValidationMetadata()
{
ValidateChildren = true,
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var validateChildren = metadata.ValidateChildren;
// Assert
Assert.True(validateChildren);
}
[Fact]
public void ValidateChildren_OverrideFalse()
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(XmlDocument));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
cache.ValidationMetadata = new ValidationMetadata()
{
ValidateChildren = false,
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var validateChildren = metadata.ValidateChildren;
// Assert
Assert.False(validateChildren);
}
private void ActionMethod(string input) private void ActionMethod(string input)
{ {
} }

View File

@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], modelMetadataProvider), new DefaultObjectValidator(modelMetadataProvider),
validator); validator);
// Assert // Assert
@ -136,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider), new DefaultObjectValidator(metadataProvider),
validator); validator);
// Assert // Assert
@ -221,7 +221,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider), new DefaultObjectValidator(metadataProvider),
validator, validator,
includePredicate); includePredicate);
@ -305,7 +305,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider), new DefaultObjectValidator(metadataProvider),
validator, validator,
m => m.IncludedProperty, m => m.IncludedProperty,
m => m.MyProperty); m => m.MyProperty);
@ -357,7 +357,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider), new DefaultObjectValidator(metadataProvider),
validator); validator);
// Assert // Assert
@ -568,9 +568,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator( new DefaultObjectValidator(metadataProvider),
new IExcludeTypeValidationFilter[0],
metadataProvider),
validator, validator,
includePredicate); includePredicate);
@ -645,9 +643,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binders), GetCompositeBinder(binders),
valueProvider, valueProvider,
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator( new DefaultObjectValidator(metadataProvider),
new IExcludeTypeValidationFilter[0],
metadataProvider),
validator); validator);
// Assert // Assert
@ -679,9 +675,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
GetCompositeBinder(binder.Object), GetCompositeBinder(binder.Object),
Mock.Of<IValueProvider>(), Mock.Of<IValueProvider>(),
new List<IInputFormatter>(), new List<IInputFormatter>(),
new DefaultObjectValidator( new DefaultObjectValidator(metadataProvider),
new IExcludeTypeValidationFilter[0],
metadataProvider),
Mock.Of<IModelValidatorProvider>(), Mock.Of<IModelValidatorProvider>(),
includePredicate)); includePredicate));

View File

@ -8,6 +8,7 @@ using System.Linq;
#if DNXCORE50 #if DNXCORE50
using System.Reflection; using System.Reflection;
#endif #endif
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -655,13 +656,40 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage); Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("Profession"), error.ErrorMessage);
} }
public static TheoryData<object, Type> ValidCollectionData
{
get
{
return new TheoryData<object, Type>()
{
{ new int[] { 1, 2, 3 }, typeof(int[]) },
{ new string[] { "Foo", "Bar", "Baz" }, typeof(string[]) },
{ new List<string> { "Foo", "Bar", "Baz" }, typeof(IList<string>)},
{ new HashSet<string> { "Foo", "Bar", "Baz" }, typeof(string[]) },
{
new List<DateTime>
{
DateTime.Parse("1/1/14"),
DateTime.Parse("2/1/14"),
DateTime.Parse("3/1/14"),
},
typeof(ICollection<DateTime>)
},
{
new HashSet<Uri>
{
new Uri("http://example.com/1"),
new Uri("http://example.com/2"),
new Uri("http://example.com/3"),
},
typeof(HashSet<Uri>)
},
};
}
}
[Theory] [Theory]
[InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(string[]))] [MemberData(nameof(ValidCollectionData))]
[InlineData(new[] { 1, 2, 3 }, typeof(int[]))]
[InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(IList<string>))]
[InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(HashSet<string>))]
[InlineData(new[] { "1/1/14", "2/2/14", "3/3/14" }, typeof(ICollection<DateTime>))]
[InlineData(new[] { "Foo", "Bar", "Baz" }, typeof(HashSet<Uri>))]
public void Validate_IndexedCollectionTypes_Valid(object model, Type type) public void Validate_IndexedCollectionTypes_Valid(object model, Type type)
{ {
// Arrange // Arrange
@ -670,7 +698,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
var modelState = actionContext.ModelState; var modelState = actionContext.ModelState;
var validationState = new ValidationStateDictionary(); var validationState = new ValidationStateDictionary();
var validator = CreateValidator(new SimpleTypesExcludeFilter()); var validator = CreateValidator();
modelState.Add("items[0]", new ModelStateEntry()); modelState.Add("items[0]", new ModelStateEntry());
modelState.Add("items[1]", new ModelStateEntry()); modelState.Add("items[1]", new ModelStateEntry());
@ -712,7 +740,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
var modelState = actionContext.ModelState; var modelState = actionContext.ModelState;
var validationState = new ValidationStateDictionary(); var validationState = new ValidationStateDictionary();
var validator = CreateValidator(new SimpleTypesExcludeFilter()); var validator = CreateValidator();
var model = new Dictionary<string, string>() var model = new Dictionary<string, string>()
{ {
@ -734,19 +762,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
AssertKeysEqual(modelState, "items[0].Key", "items[0].Value", "items[1].Key", "items[1].Value"); AssertKeysEqual(modelState, "items[0].Key", "items[0].Value", "items[1].Key", "items[1].Value");
var entry = modelState["items[0].Key"]; var entry = modelState["items[0].Key"];
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
Assert.Empty(entry.Errors); Assert.Empty(entry.Errors);
entry = modelState["items[0].Value"]; entry = modelState["items[0].Value"];
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
Assert.Empty(entry.Errors); Assert.Empty(entry.Errors);
entry = modelState["items[1].Key"]; entry = modelState["items[1].Key"];
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
Assert.Empty(entry.Errors); Assert.Empty(entry.Errors);
entry = modelState["items[1].Value"]; entry = modelState["items[1].Value"];
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
Assert.Empty(entry.Errors); Assert.Empty(entry.Errors);
} }
@ -857,7 +885,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
} }
[Fact] [Fact]
public void Validate_ForExcludedType_PropertiesMarkedAsSkipped() public void Validate_ForExcludedComplexType_PropertiesMarkedAsSkipped()
{ {
// Arrange // Arrange
var validatorProvider = CreateValidatorProvider(); var validatorProvider = CreateValidatorProvider();
@ -890,6 +918,37 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
Assert.Empty(entry.Errors); Assert.Empty(entry.Errors);
} }
[Fact]
public void Validate_ForExcludedCollectionType_PropertiesMarkedAsSkipped()
{
// Arrange
var validatorProvider = CreateValidatorProvider();
var actionContext = new ActionContext();
var modelState = actionContext.ModelState;
var validationState = new ValidationStateDictionary();
var validator = CreateValidator(typeof(List<string>));
var model = new List<string>()
{
"15",
};
modelState.SetModelValue("userIds[0]", "15", "15");
validationState.Add(model, new ValidationStateEntry() { Key = "userIds", });
// Act
validator.Validate(actionContext, validatorProvider, validationState, "userIds", model);
// Assert
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
AssertKeysEqual(modelState, "userIds[0]");
var entry = modelState["userIds[0]"];
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
Assert.Empty(entry.Errors);
}
private static IModelValidatorProvider CreateValidatorProvider() private static IModelValidatorProvider CreateValidatorProvider()
{ {
return TestModelValidatorProvider.CreateDefaultProvider(); return TestModelValidatorProvider.CreateDefaultProvider();
@ -897,23 +956,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
private static DefaultObjectValidator CreateValidator(Type excludedType) private static DefaultObjectValidator CreateValidator(Type excludedType)
{ {
var excludeFilters = new List<IExcludeTypeValidationFilter>(); var excludeFilters = new List<ValidationExcludeFilter>();
if (excludedType != null) if (excludedType != null)
{ {
var excludeFilter = new Mock<IExcludeTypeValidationFilter>(); excludeFilters.Add(new ValidationExcludeFilter(excludedType));
excludeFilter
.Setup(o => o.IsTypeExcluded(It.IsAny<Type>()))
.Returns<Type>(t => t.IsAssignableFrom(excludedType));
excludeFilters.Add(excludeFilter.Object);
} }
return new DefaultObjectValidator(excludeFilters, TestModelMetadataProvider.CreateDefaultProvider()); var provider = TestModelMetadataProvider.CreateDefaultProvider(excludeFilters.ToArray());
return new DefaultObjectValidator(provider);
} }
private static DefaultObjectValidator CreateValidator(params IExcludeTypeValidationFilter[] excludeFilters) private static DefaultObjectValidator CreateValidator(params IMetadataDetailsProvider[] providers)
{ {
return new DefaultObjectValidator(excludeFilters, TestModelMetadataProvider.CreateDefaultProvider()); var provider = TestModelMetadataProvider.CreateDefaultProvider(providers);
return new DefaultObjectValidator(provider);
} }
private static void AssertKeysEqual(ModelStateDictionary modelState, params string[] keys) private static void AssertKeysEqual(ModelStateDictionary modelState, params string[] keys)

View File

@ -1,45 +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 Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ExcludeTypeValidationFilterCollectionTest
{
[Fact]
public void AddFilter_ByType()
{
// Arrange
var type = typeof(BaseType);
var collection = new ExcludeTypeValidationFilterCollection();
// Act
collection.Add(type);
// Assert
var filter = Assert.IsType<DefaultTypeBasedExcludeFilter>(Assert.Single(collection));
Assert.Equal(type, filter.ExcludedType);
}
[Fact]
public void AddFilter_ByTypeName()
{
// Arrange
var type = typeof(BaseType);
var collection = new ExcludeTypeValidationFilterCollection();
// Act
collection.Add(type.FullName);
// Assert
var filter = Assert.IsType<DefaultTypeNameBasedExcludeFilter>(Assert.Single(collection));
Assert.Equal(type.FullName, filter.ExcludedTypeName);
}
private class BaseType
{
}
}
}

View File

@ -1,89 +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;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class SimpleTypeExcludeFilterTest
{
[Theory]
[MemberData(nameof(ExcludedTypes))]
public void SimpleTypeExcludeFilter_ExcludedTypes(Type type)
{
// Arrange
var filter = new SimpleTypesExcludeFilter();
// Act & Assert
Assert.True(filter.IsTypeExcluded(type));
}
[Theory]
[MemberData(nameof(IncludedTypes))]
public void SimpleTypeExcludeFilter_IncludedTypes(Type type)
{
// Arrange
var filter = new SimpleTypesExcludeFilter();
// Act & Assert
Assert.False(filter.IsTypeExcluded(type));
}
private class TestType
{
}
public static TheoryData<Type> ExcludedTypes
{
get
{
return new TheoryData<Type>()
{
// Simple types
typeof(int),
typeof(DateTime),
// Nullable types
typeof(int?),
// KeyValue types
typeof(KeyValuePair<string, string>)
};
}
}
public static TheoryData<Type> IncludedTypes
{
get
{
return new TheoryData<Type>()
{
// Enumerable types
typeof(int[]),
typeof(List<decimal>),
typeof(SortedSet<int>),
typeof(ICollection<string>),
typeof(int?[]),
typeof(SortedSet<int?>),
typeof(HashSet<Uri>),
typeof(HashSet<string>),
typeof(IList<DateTime>),
typeof(Dictionary<int, string>),
typeof(IReadOnlyDictionary<int?, char>),
// Complex types
typeof(TestType),
typeof(TestType[]),
typeof(SortedSet<TestType>),
typeof(Dictionary<int, TestType>),
typeof(Dictionary<TestType, int>),
typeof(Dictionary<TestType, TestType>),
typeof(KeyValuePair<string, TestType>)
};
}
}
}
}

View File

@ -129,14 +129,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low].Key").Value; var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low].Key").Value;
Assert.Equal("key0", entry.AttemptedValue); Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue); Assert.Equal("key0", entry.RawValue);
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
// Key and Value are skipped if they have simple types.
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low].Value").Value; entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low].Value").Value;
Assert.Equal("10", entry.AttemptedValue); Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue); Assert.Equal("10", entry.RawValue);
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
} }
[Theory] [Theory]

View File

@ -104,9 +104,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
binding.ModelBindingMessageProvider.MissingKeyOrValueAccessor = () => $"Hurts when nothing is provided."; binding.ModelBindingMessageProvider.MissingKeyOrValueAccessor = () => $"Hurts when nothing is provided.";
})); }));
var argumentBinder = new DefaultControllerActionArgumentBinder( var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
metadataProvider,
ModelBindingTestHelper.GetObjectValidator());
var parameter = new ParameterDescriptor var parameter = new ParameterDescriptor
{ {
Name = "parameter", Name = "parameter",
@ -188,9 +186,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// A real details provider could customize message based on BindingMetadataProviderContext. // A real details provider could customize message based on BindingMetadataProviderContext.
binding.ModelBindingMessageProvider.MissingKeyOrValueAccessor = () => $"Hurts when nothing is provided."; binding.ModelBindingMessageProvider.MissingKeyOrValueAccessor = () => $"Hurts when nothing is provided.";
})); }));
var argumentBinder = new DefaultControllerActionArgumentBinder(
metadataProvider, var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
ModelBindingTestHelper.GetObjectValidator());
var parameter = new ParameterDescriptor var parameter = new ParameterDescriptor
{ {

View File

@ -42,20 +42,28 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public static DefaultControllerActionArgumentBinder GetArgumentBinder(MvcOptions options = null) public static DefaultControllerActionArgumentBinder GetArgumentBinder(MvcOptions options = null)
{ {
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); if (options == null)
return new DefaultControllerActionArgumentBinder( {
metadataProvider, var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
GetObjectValidator(options)); return GetArgumentBinder(metadataProvider);
}
else
{
var metadataProvider = TestModelMetadataProvider.CreateProvider(options.ModelMetadataDetailsProviders);
return GetArgumentBinder(metadataProvider);
}
} }
public static IObjectModelValidator GetObjectValidator(MvcOptions options = null) public static DefaultControllerActionArgumentBinder GetArgumentBinder(IModelMetadataProvider metadataProvider)
{ {
options = options ?? new TestMvcOptions().Value; return new DefaultControllerActionArgumentBinder(
options.MaxModelValidationErrors = 5; metadataProvider,
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); GetObjectValidator(metadataProvider));
return new DefaultObjectValidator( }
options.ValidationExcludeFilters,
metadataProvider); public static IObjectModelValidator GetObjectValidator(IModelMetadataProvider metadataProvider)
{
return new DefaultObjectValidator(metadataProvider);
} }
private static HttpContext GetHttpContext( private static HttpContext GetHttpContext(

View File

@ -1424,9 +1424,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
binding.ModelBindingMessageProvider.MissingBindRequiredValueAccessor = binding.ModelBindingMessageProvider.MissingBindRequiredValueAccessor =
name => $"Hurts when '{ name }' is not provided."; name => $"Hurts when '{ name }' is not provided.";
})); }));
var argumentBinder = new DefaultControllerActionArgumentBinder(
metadataProvider, var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
ModelBindingTestHelper.GetObjectValidator());
var parameter = new ParameterDescriptor() var parameter = new ParameterDescriptor()
{ {
Name = "parameter", Name = "parameter",

View File

@ -312,9 +312,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
binding.ModelBindingMessageProvider.ValueMustNotBeNullAccessor = binding.ModelBindingMessageProvider.ValueMustNotBeNullAccessor =
value => $"Hurts when '{ value }' is provided."; value => $"Hurts when '{ value }' is provided.";
})); }));
var argumentBinder = new DefaultControllerActionArgumentBinder( var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(metadataProvider);
metadataProvider,
ModelBindingTestHelper.GetObjectValidator());
var parameter = new ParameterDescriptor var parameter = new ParameterDescriptor
{ {
Name = "Parameter1", Name = "Parameter1",

View File

@ -1109,7 +1109,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
operationContext.ModelBinder, operationContext.ModelBinder,
operationContext.ValueProvider, operationContext.ValueProvider,
operationContext.InputFormatters, operationContext.InputFormatters,
ModelBindingTestHelper.GetObjectValidator(), ModelBindingTestHelper.GetObjectValidator(operationContext.MetadataProvider),
operationContext.ValidatorProvider); operationContext.ValidatorProvider);
} }
} }

View File

@ -9,6 +9,8 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests namespace Microsoft.AspNet.Mvc.IntegrationTests
@ -1129,7 +1131,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
}, },
options => options =>
{ {
options.ValidationExcludeFilters.Add(typeof(Address)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(Address)));
testOptions = options; testOptions = options;
}); });
@ -1167,6 +1169,55 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal(ModelValidationState.Valid, entry.ValidationState); Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
} }
[Fact]
public async Task FromBody_JToken_ExcludedFromValidation()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(new TestMvcOptions().Value);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
BindingSource = BindingSource.Body
},
ParameterType = typeof(JToken)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{ message: \"Hello\" }"));
request.ContentType = "application/json";
});
var httpContext = operationContext.HttpContext;
var modelState = operationContext.ActionContext.ModelState;
// We need to add another model state entry which should get marked as skipped so
// we can prove that the JObject was skipped.
modelState.SetModelValue("message", "Hello", "Hello");
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, operationContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
Assert.NotNull(modelBindingResult.Model);
var message = Assert.IsType<JObject>(modelBindingResult.Model).GetValue("message").Value<string>();
Assert.Equal("Hello", message);
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Count);
var entry = Assert.Single(modelState, kvp => kvp.Key == string.Empty);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
entry = Assert.Single(modelState, kvp => kvp.Key == "message");
Assert.Equal(ModelValidationState.Skipped, entry.Value.ValidationState);
}
private static void AssertRequiredError(string key, ModelError error) private static void AssertRequiredError(string key, ModelError error)
{ {
// Mono issue - https://github.com/aspnet/External/issues/19 // Mono issue - https://github.com/aspnet/External/issues/19

View File

@ -19,6 +19,9 @@ using Moq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Threading;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -134,7 +137,7 @@ namespace Microsoft.AspNet.Mvc
} }
[Fact] [Fact]
public void Setup_SetsUpExcludeFromValidationDelegates() public void Setup_SetsUpMetadataDetailsProviders()
{ {
// Arrange & Act // Arrange & Act
var options = GetOptions<MvcOptions>(services => var options = GetOptions<MvcOptions>(services =>
@ -144,48 +147,40 @@ namespace Microsoft.AspNet.Mvc
}); });
// Assert // Assert
Assert.Equal(8, options.ValidationExcludeFilters.Count); var providers = options.ModelMetadataDetailsProviders;
Assert.Equal(12, providers.Count);
var i = 0; var i = 0;
// Verify if the delegates registered by default exclude the given types. Assert.IsType<DefaultBindingMetadataProvider>(providers[i++]);
Assert.IsType(typeof(SimpleTypesExcludeFilter), options.ValidationExcludeFilters[i++]); Assert.IsType<DefaultValidationMetadataProvider>(providers[i++]);
Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), options.ValidationExcludeFilters[i]); var excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
var typeFilter Assert.Equal(typeof(Type), excludeFilter.Type);
= Assert.IsType<DefaultTypeBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(typeFilter.ExcludedType, typeof(Type));
Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), options.ValidationExcludeFilters[i]); excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
var cancellationTokenFilter Assert.Equal(typeof(Uri), excludeFilter.Type);
= Assert.IsType<DefaultTypeBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(cancellationTokenFilter.ExcludedType, typeof(System.Threading.CancellationToken));
Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), options.ValidationExcludeFilters[i]); excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
var formFileFilter Assert.Equal(typeof(CancellationToken), excludeFilter.Type);
= Assert.IsType<DefaultTypeBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(formFileFilter.ExcludedType, typeof(Http.IFormFile));
Assert.IsType( excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
typeof(DefaultTypeBasedExcludeFilter), Assert.Equal(typeof(IFormFile), excludeFilter.Type);
options.ValidationExcludeFilters[i]);
var formCollectionFilter
= Assert.IsType<DefaultTypeBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(formCollectionFilter.ExcludedType, typeof(Http.IFormCollection));
Assert.IsType(typeof(DefaultTypeBasedExcludeFilter), options.ValidationExcludeFilters[i]); excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
var jTokenFilter Assert.Equal(typeof(IFormCollection), excludeFilter.Type);
= Assert.IsType<DefaultTypeBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(jTokenFilter.ExcludedType, typeof(JToken));
Assert.IsType(typeof(DefaultTypeNameBasedExcludeFilter), options.ValidationExcludeFilters[i]); Assert.IsType<DataAnnotationsMetadataProvider>(providers[i++]);
var xObjectFilter
= Assert.IsType<DefaultTypeNameBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(xObjectFilter.ExcludedTypeName, typeof(XObject).FullName);
Assert.IsType(typeof(DefaultTypeNameBasedExcludeFilter), options.ValidationExcludeFilters[i]); excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
var xmlNodeFilter = Assert.Equal(typeof(JToken), excludeFilter.Type);
Assert.IsType<DefaultTypeNameBasedExcludeFilter>(options.ValidationExcludeFilters[i++]);
Assert.Equal(xmlNodeFilter.ExcludedTypeName, "System.Xml.XmlNode"); Assert.IsType<DataMemberRequiredBindingMetadataProvider>(providers[i++]);
excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
Assert.Equal(typeof(XObject).FullName, excludeFilter.FullTypeName);
excludeFilter = Assert.IsType<ValidationExcludeFilter>(providers[i++]);
Assert.Equal("System.Xml.XmlNode", excludeFilter.FullTypeName);
} }
[Fact] [Fact]

View File

@ -26,6 +26,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return new DefaultModelMetadataProvider(compositeDetailsProvider); return new DefaultModelMetadataProvider(compositeDetailsProvider);
} }
public static IModelMetadataProvider CreateDefaultProvider(IList<IMetadataDetailsProvider> providers)
{
var detailsProviders = new List<IMetadataDetailsProvider>()
{
new DefaultBindingMetadataProvider(CreateMessageProvider()),
new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(),
new DataMemberRequiredBindingMetadataProvider(),
};
detailsProviders.AddRange(providers);
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
return new DefaultModelMetadataProvider(compositeDetailsProvider);
}
public static IModelMetadataProvider CreateProvider(IList<IMetadataDetailsProvider> providers)
{
var detailsProviders = new List<IMetadataDetailsProvider>();
if (providers != null)
{
detailsProviders.AddRange(providers);
}
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
return new DefaultModelMetadataProvider(compositeDetailsProvider);
}
private readonly TestModelMetadataDetailsProvider _detailsProvider; private readonly TestModelMetadataDetailsProvider _detailsProvider;
public TestModelMetadataProvider() public TestModelMetadataProvider()

View File

@ -1873,7 +1873,7 @@ namespace Microsoft.AspNet.Mvc.Test
{ {
ControllerContext = controllerContext, ControllerContext = controllerContext,
MetadataProvider = metadataProvider, MetadataProvider = metadataProvider,
ObjectValidator = new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider), ObjectValidator = new DefaultObjectValidator(metadataProvider),
TempData = tempData, TempData = tempData,
ViewData = viewData, ViewData = viewData,
}; };

View File

@ -3,6 +3,7 @@
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FormatterWebSite namespace FormatterWebSite
@ -13,8 +14,8 @@ namespace FormatterWebSite
{ {
services.AddMvc(options => services.AddMvc(options =>
{ {
options.ValidationExcludeFilters.Add(typeof(Developer)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(Developer)));
options.ValidationExcludeFilters.Add(typeof(Supplier)); options.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(Supplier)));
options.InputFormatters.Add(new StringInputFormatter()); options.InputFormatters.Add(new StringInputFormatter());
}) })

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModelBindingWebSite.Models; using ModelBindingWebSite.Models;
using ModelBindingWebSite.Services; using ModelBindingWebSite.Services;
@ -20,7 +21,7 @@ namespace ModelBindingWebSite
m.MaxModelValidationErrors = 8; m.MaxModelValidationErrors = 8;
m.ModelBinders.Insert(0, new TestBindingSourceModelBinder()); m.ModelBinders.Insert(0, new TestBindingSourceModelBinder());
m.ValidationExcludeFilters.Add(typeof(Address)); m.ModelMetadataDetailsProviders.Add(new ValidationExcludeFilter(typeof(Address)));
// ModelMetadataController relies on additional values AdditionalValuesMetadataProvider provides. // ModelMetadataController relies on additional values AdditionalValuesMetadataProvider provides.
m.ModelMetadataDetailsProviders.Add(new AdditionalValuesMetadataProvider()); m.ModelMetadataDetailsProviders.Add(new AdditionalValuesMetadataProvider());