Adding support for excluding types for validation, based on type names for body bound models.

This commit is contained in:
Harsh Gupta 2014-10-22 15:09:06 -07:00
parent bff70e0dd1
commit 6b2f331e8d
30 changed files with 623 additions and 167 deletions

View File

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.OptionDescriptors
{
/// <inheritdoc />
public class DefaultValidationExcludeFiltersProvider
: OptionDescriptorBasedProvider<IExcludeTypeValidationFilter>, IValidationExcludeFiltersProvider
{
/// <summary>
/// Initializes a new instance of the DefaultBodyValidationExcludeFiltersProvider class.
/// </summary>
/// <param name="options">An accessor to the <see cref="MvcOptions"/> configured for this application.</param>
/// <param name="typeActivator">An <see cref="ITypeActivator"/> instance used to instantiate types.</param>
/// <param name="serviceProvider">A <see cref="IServiceProvider"/> instance that retrieves services from the
/// service collection.</param>
public DefaultValidationExcludeFiltersProvider(IOptions<MvcOptions> optionsAccessor,
ITypeActivator typeActivator,
IServiceProvider serviceProvider)
: base(optionsAccessor.Options.ValidationExcludeFilters, typeActivator, serviceProvider)
{
}
/// <inheritdoc />
public IReadOnlyList<IExcludeTypeValidationFilter> ExcludeFilters
{
get
{
return Options;
}
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <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([NotNull] Type type)
{
ExcludedType = type;
}
/// <summary>
/// Gets the type which is excluded from validation.
/// </summary>
public Type ExcludedType { get; }
/// <inheritdoc />
public bool IsTypeExcluded([NotNull] Type propertyType)
{
return ExcludedType.IsAssignableFrom(propertyType);
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <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([NotNull] string typeFullName)
{
ExcludedTypeName = typeFullName;
}
/// <summary>
/// Gets the type full name which is excluded from validation.
/// </summary>
public string ExcludedTypeName { get; }
/// <inheritdoc />
public bool IsTypeExcluded([NotNull] Type 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.BaseType());
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Provides an activated collection of <see cref="IExcludeTypeValidationFilter"/> instances.
/// </summary>
public interface IValidationExcludeFiltersProvider
{
/// <summary>
/// Gets a collection of activated <see cref="IExcludeTypeValidationFilter"/> instances.
/// </summary>
IReadOnlyList<IExcludeTypeValidationFilter> ExcludeFilters { get; }
}
}

View File

@ -1,14 +1,10 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -21,17 +17,17 @@ namespace Microsoft.AspNet.Mvc
private readonly ActionContext _actionContext; private readonly ActionContext _actionContext;
private readonly IInputFormatterSelector _formatterSelector; private readonly IInputFormatterSelector _formatterSelector;
private readonly IBodyModelValidator _bodyModelValidator; private readonly IBodyModelValidator _bodyModelValidator;
private readonly IOptions<MvcOptions> _mvcOptions; private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider;
public BodyModelBinder([NotNull] IContextAccessor<ActionContext> context, public BodyModelBinder([NotNull] IContextAccessor<ActionContext> context,
[NotNull] IInputFormatterSelector selector, [NotNull] IInputFormatterSelector selector,
[NotNull] IBodyModelValidator bodyModelValidator, [NotNull] IBodyModelValidator bodyModelValidator,
[NotNull] IOptions<MvcOptions> mvcOptions) [NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider)
{ {
_actionContext = context.Value; _actionContext = context.Value;
_formatterSelector = selector; _formatterSelector = selector;
_bodyModelValidator = bodyModelValidator; _bodyModelValidator = bodyModelValidator;
_mvcOptions = mvcOptions; _bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider;
} }
protected override async Task<bool> BindAsync( protected override async Task<bool> BindAsync(
@ -60,7 +56,7 @@ namespace Microsoft.AspNet.Mvc
bindingContext.ModelState, bindingContext.ModelState,
bindingContext.ModelMetadata, bindingContext.ModelMetadata,
containerMetadata: null, containerMetadata: null,
excludeFromValidationDelegate: _mvcOptions.Options.ExcludeFromValidationDelegates); excludeFromValidationFilters: _bodyValidationExcludeFiltersProvider.ExcludeFilters);
_bodyModelValidator.Validate(validationContext, bindingContext.ModelName); _bodyModelValidator.Validate(validationContext, bindingContext.ModelName);
return true; return true;
} }

View File

@ -1,26 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Extensions for <see cref="MvcOptions.ExcludeFromValidationDelegates"/>.
/// </summary>
public static class ExcludeFromValidationDelegateExtensions
{
/// <summary>
/// Adds a delegate to the specified <paramref name="list" />
/// that exludes the properties of the specified and it's derived types from validaton.
/// </summary>
/// <param name="list"><see cref="IList{T}"/> of <see cref="ExcludeFromValidationDelegate"/>.</param>
/// <param name="type"><see cref="Type"/> which should be excluded from validation.</param>
public static void Add(this IList<ExcludeFromValidationDelegate> list, Type type)
{
list.Add(t => t.IsAssignableFrom(type));
}
}
}

View File

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc.OptionDescriptors
{
/// <summary>
/// Encapsulates information that describes an <see cref="IExcludeTypeValidationFilter"/>.
/// </summary>
public class ExcludeValidationDescriptor : OptionDescriptor<IExcludeTypeValidationFilter>
{
/// <summary>
/// Creates a new instance of <see cref="ExcludeValidationDescriptor"/>.
/// </summary>
/// <param name="type">
/// A <see cref="IExcludeTypeValidationFilter"/> type that the descriptor represents.
/// </param>
public ExcludeValidationDescriptor([NotNull] Type type)
: base(type)
{
}
/// <summary>
/// Creates a new instance of <see cref="ExcludeValidationDescriptor"/>.
/// </summary>
/// <param name="validationFilter">An instance of <see cref="IExcludeTypeValidationFilter"/>
/// that the descriptor represents.</param>
public ExcludeValidationDescriptor([NotNull] IExcludeTypeValidationFilter validationFilter)
: base(validationFilter)
{
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 Microsoft.AspNet.Mvc.OptionDescriptors;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Extensions for <see cref="MvcOptions.ValidationExcludeFilters"/>.
/// </summary>
public static class ValidationExcludeFiltersExtensions
{
/// <summary>
/// Adds a descriptor to the specified <paramref name="excludeBodyValidationDescriptorCollection" />
/// that excludes the properties of the <see cref="Type"/> specified and it's derived types from validaton.
/// </summary>
/// <param name="excludeBodyValidationDescriptorCollection">A list of <see cref="ExcludeValidationDescriptor"/>
/// which are used to get a collection of exclude filters to be applied for filtering model properties during validation.
/// </param>
/// <param name="type"><see cref="Type"/> which should be excluded from validation.</param>
public static void Add(this IList<ExcludeValidationDescriptor> excludeBodyValidationDescriptorCollection,
Type type)
{
var typeBasedExcludeFilter = new DefaultTypeBasedExcludeFilter(type);
excludeBodyValidationDescriptorCollection.Add(new ExcludeValidationDescriptor(typeBasedExcludeFilter));
}
/// <summary>
/// Adds a descriptor to the specified <paramref name="excludeBodyValidationDescriptorCollection" />
/// that excludes the properties of the type specified and it's derived types from validaton.
/// </summary>
/// <param name="excludeBodyValidationDescriptorCollection">A list of <see cref="ExcludeValidationDescriptor"/>
/// which are used to get a collection of exclude filters to be applied for filtering model properties during validation.
/// </param>
/// <param name="typeFullName">Full name of the type which should be excluded from validation.</param>
public static void Add(this IList<ExcludeValidationDescriptor> excludeBodyValidationDescriptorCollection,
string typeFullName)
{
var filter = new DefaultTypeNameBasedExcludeFilter(typeFullName);
excludeBodyValidationDescriptorCollection.Add(new ExcludeValidationDescriptor(filter));
}
}
}

View File

@ -71,11 +71,11 @@ namespace Microsoft.AspNet.Mvc
public List<InputFormatterDescriptor> InputFormatters { get; private set; } public List<InputFormatterDescriptor> InputFormatters { get; private set; }
/// <summary> /// <summary>
/// Gets a list of <see cref="ExcludeFromValidationDelegate"/> which return whether the given type /// Gets a list of <see cref="ExcludeValidationDescriptor"/> which are used to construct a list
/// should be excluded from Validation in <see cref="IBodyModelValidator"/> /// of exclude filters by <see cref="IValidationExcludeFiltersProvider"/>,
/// </summary> /// </summary>
public List<ExcludeFromValidationDelegate> ExcludeFromValidationDelegates { get; } public List<ExcludeValidationDescriptor> ValidationExcludeFilters { get; }
= new List<ExcludeFromValidationDelegate>(); = new List<ExcludeValidationDescriptor>();
/// <summary> /// <summary>
/// Gets or sets the maximum number of validation errors that are allowed by this application before further /// Gets or sets the maximum number of validation errors that are allowed by this application before further

View File

@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelType = metadata.Model.GetType(); var modelType = metadata.Model.GetType();
if (TypeHelper.IsSimpleType(modelType) || if (TypeHelper.IsSimpleType(modelType) ||
IsTypeExcludedFromValidation( IsTypeExcludedFromValidation(
validationContext.ModelValidationContext.ExcludeFromValidationDelegate, modelType)) validationContext.ModelValidationContext.ExcludeFromValidationFilters, modelType))
{ {
return ShallowValidate(metadata, validationContext, validators); return ShallowValidate(metadata, validationContext, validators);
} }
@ -197,15 +197,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
private bool IsTypeExcludedFromValidation( private bool IsTypeExcludedFromValidation(
IReadOnlyList<ExcludeFromValidationDelegate> predicates, Type type) IReadOnlyList<IExcludeTypeValidationFilter> filters, Type type)
{ {
// This can be set to null in ModelBinding scenarios which does not flow through this path. // This can be set to null in ModelBinding scenarios which does not flow through this path.
if (predicates == null) if (filters == null)
{ {
return false; return false;
} }
return predicates.Any(t => t(type)); return filters.Any(filter => filter.IsTypeExcluded(type));
} }
private static Type GetElementType(Type type) private static Type GetElementType(Type type)

View File

@ -1,14 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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
{
/// <summary>
/// Delegate that determines if the specified <paramref name="type"/> is excluded from validation.
/// </summary>
/// <param name="type"><see cref="Type"/> which needs to be checked.</param>
/// <returns><c>true</c> if excluded, <c>false</c> otherwise.</returns>
public delegate bool ExcludeFromValidationDelegate(Type type);
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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
{
/// <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([NotNull] Type type);
}
}

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
modelState, modelState,
metadata, metadata,
containerMetadata, containerMetadata,
excludeFromValidationDelegate: null) excludeFromValidationFilters: null)
{ {
} }
@ -36,14 +36,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[NotNull] ModelStateDictionary modelState, [NotNull] ModelStateDictionary modelState,
[NotNull] ModelMetadata metadata, [NotNull] ModelMetadata metadata,
ModelMetadata containerMetadata, ModelMetadata containerMetadata,
IReadOnlyList<ExcludeFromValidationDelegate> excludeFromValidationDelegate) IReadOnlyList<IExcludeTypeValidationFilter> excludeFromValidationFilters)
{ {
ModelMetadata = metadata; ModelMetadata = metadata;
ModelState = modelState; ModelState = modelState;
MetadataProvider = metadataProvider; MetadataProvider = metadataProvider;
ValidatorProvider = validatorProvider; ValidatorProvider = validatorProvider;
ContainerMetadata = containerMetadata; ContainerMetadata = containerMetadata;
ExcludeFromValidationDelegate = excludeFromValidationDelegate; ExcludeFromValidationFilters = excludeFromValidationFilters;
} }
public ModelValidationContext([NotNull] ModelValidationContext parentContext, public ModelValidationContext([NotNull] ModelValidationContext parentContext,
@ -54,19 +54,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelState = parentContext.ModelState; ModelState = parentContext.ModelState;
MetadataProvider = parentContext.MetadataProvider; MetadataProvider = parentContext.MetadataProvider;
ValidatorProvider = parentContext.ValidatorProvider; ValidatorProvider = parentContext.ValidatorProvider;
ExcludeFromValidationDelegate = parentContext.ExcludeFromValidationDelegate; ExcludeFromValidationFilters = parentContext.ExcludeFromValidationFilters;
} }
public ModelMetadata ModelMetadata { get; private set; } public ModelMetadata ModelMetadata { get; }
public ModelMetadata ContainerMetadata { get; private set; } public ModelMetadata ContainerMetadata { get; }
public ModelStateDictionary ModelState { get; private set; } public ModelStateDictionary ModelState { get; }
public IModelMetadataProvider MetadataProvider { get; private set; } public IModelMetadataProvider MetadataProvider { get; }
public IModelValidatorProvider ValidatorProvider { get; private set; } public IModelValidatorProvider ValidatorProvider { get; }
public IReadOnlyList<ExcludeFromValidationDelegate> ExcludeFromValidationDelegate { get; private set; } public IReadOnlyList<IExcludeTypeValidationFilter> ExcludeFromValidationFilters { get; }
} }
} }

View File

@ -12,7 +12,6 @@ using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.AspNet.Mvc.WebApiCompatShim;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Newtonsoft.Json; using Newtonsoft.Json;
using MvcMediaTypeHeaderValue = Microsoft.AspNet.Mvc.HeaderValueAbstractions.MediaTypeHeaderValue; using MvcMediaTypeHeaderValue = Microsoft.AspNet.Mvc.HeaderValueAbstractions.MediaTypeHeaderValue;
@ -402,7 +401,8 @@ namespace System.Web.Http
/// </param> /// </param>
public void Validate<TEntity>(TEntity entity, string keyPrefix) public void Validate<TEntity>(TEntity entity, string keyPrefix)
{ {
var mvcOptions = Context.RequestServices.GetRequiredService<IOptions<MvcOptions>>(); var bodyValidationExcludeFiltersProvider = Context.RequestServices
.GetRequiredService<IValidationExcludeFiltersProvider>();
var validator = Context.RequestServices.GetRequiredService<IBodyModelValidator>(); var validator = Context.RequestServices.GetRequiredService<IBodyModelValidator>();
var metadataProvider = Context.RequestServices.GetRequiredService<IModelMetadataProvider>(); var metadataProvider = Context.RequestServices.GetRequiredService<IModelMetadataProvider>();
var modelMetadata = metadataProvider.GetMetadataForType(() => entity, typeof(TEntity)); var modelMetadata = metadataProvider.GetMetadataForType(() => entity, typeof(TEntity));
@ -413,7 +413,7 @@ namespace System.Web.Http
ModelState, ModelState,
modelMetadata, modelMetadata,
containerMetadata: null, containerMetadata: null,
excludeFromValidationDelegate: mvcOptions.Options.ExcludeFromValidationDelegates); excludeFromValidationFilters: bodyValidationExcludeFiltersProvider.ExcludeFilters);
validator.Validate(modelValidationContext, keyPrefix); validator.Validate(modelValidationContext, keyPrefix);
} }

View File

@ -58,11 +58,11 @@ namespace Microsoft.AspNet.Mvc
options.ModelValidatorProviders.Add(new DataMemberModelValidatorProvider()); options.ModelValidatorProviders.Add(new DataMemberModelValidatorProvider());
// Add types to be excluded from Validation // Add types to be excluded from Validation
options.ExcludeFromValidationDelegates.Add(typeof(XmlNode)); options.ValidationExcludeFilters.Add(typeof(XObject));
options.ExcludeFromValidationDelegates.Add(typeof(XObject)); options.ValidationExcludeFilters.Add(typeof(Type));
options.ExcludeFromValidationDelegates.Add(typeof(Type)); options.ValidationExcludeFilters.Add(typeof(byte[]));
options.ExcludeFromValidationDelegates.Add(typeof(byte[])); options.ValidationExcludeFilters.Add(typeof(JToken));
options.ExcludeFromValidationDelegates.Add(typeof(JToken)); options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.XmlNode");
} }
} }
} }

View File

@ -99,6 +99,7 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Transient<IModelValidatorProviderProvider, DefaultModelValidatorProviderProvider>(); yield return describe.Transient<IModelValidatorProviderProvider, DefaultModelValidatorProviderProvider>();
yield return describe.Scoped<ICompositeModelValidatorProvider, CompositeModelValidatorProvider>(); yield return describe.Scoped<ICompositeModelValidatorProvider, CompositeModelValidatorProvider>();
yield return describe.Transient<IBodyModelValidator, DefaultBodyModelValidator>(); yield return describe.Transient<IBodyModelValidator, DefaultBodyModelValidator>();
yield return describe.Transient<IValidationExcludeFiltersProvider, DefaultValidationExcludeFiltersProvider>();
// //
// Razor, Views and runtime compilation // Razor, Views and runtime compilation

View File

@ -10,10 +10,6 @@
}, },
"frameworks": { "frameworks": {
"aspnet50": {}, "aspnet50": {},
"aspnetcore50": { "aspnetcore50": {}
"dependencies": {
"System.Xml.XmlDocument": "4.0.0-beta-*"
}
}
} }
} }

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object); var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object);
bindingContext.ModelMetadata.BinderMetadata = Mock.Of<IFormatterBinderMetadata>(); bindingContext.ModelMetadata.BinderMetadata = Mock.Of<IFormatterBinderMetadata>();
var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object, null); var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object);
// Act // Act
var binderResult = await binder.BindModelAsync(bindingContext); var binderResult = await binder.BindModelAsync(bindingContext);
@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType), ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
ModelName = "someName", ModelName = "someName",
ValueProvider = Mock.Of<IValueProvider>(), ValueProvider = Mock.Of<IValueProvider>(),
ModelBinder = GetBodyBinder(inputFormatter, null, null), ModelBinder = GetBodyBinder(inputFormatter, null),
MetadataProvider = metadataProvider, MetadataProvider = metadataProvider,
HttpContext = new DefaultHttpContext(), HttpContext = new DefaultHttpContext(),
ModelState = new ModelStateDictionary() ModelState = new ModelStateDictionary()
@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc
} }
private static BodyModelBinder GetBodyBinder( private static BodyModelBinder GetBodyBinder(
IInputFormatter inputFormatter, IBodyModelValidator validator, IOptions<MvcOptions> mvcOptions) IInputFormatter inputFormatter, IBodyModelValidator validator)
{ {
var actionContext = CreateActionContext(new DefaultHttpContext()); var actionContext = CreateActionContext(new DefaultHttpContext());
var inputFormatterSelector = new Mock<IInputFormatterSelector>(); var inputFormatterSelector = new Mock<IInputFormatterSelector>();
@ -113,19 +113,14 @@ namespace Microsoft.AspNet.Mvc
validator = mockValidator.Object; validator = mockValidator.Object;
} }
if (mvcOptions == null) var bodyValidationPredicatesProvidwer = new Mock<IValidationExcludeFiltersProvider>();
{ bodyValidationPredicatesProvidwer.SetupGet(o => o.ExcludeFilters)
var options = new Mock<MvcOptions>(); .Returns(new List<IExcludeTypeValidationFilter>());
options.CallBase = true;
var mockMvcOptions = new Mock<IOptions<MvcOptions>>();
mockMvcOptions.SetupGet(o => o.Options).Returns(options.Object);
mvcOptions = mockMvcOptions.Object;
}
var binder = new BodyModelBinder(actionContext, var binder = new BodyModelBinder(actionContext,
inputFormatterSelector.Object, inputFormatterSelector.Object,
validator, validator,
mvcOptions); bodyValidationPredicatesProvidwer.Object);
return binder; return binder;
} }

View File

@ -0,0 +1,138 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.OptionDescriptors
{
public class DefaultValidationExcludeFiltersProviderTests
{
[Theory]
[InlineData(typeof(BaseType))]
[InlineData(typeof(DerivedType))]
public void Add_WithType_RegistersTypesAndDerivedType_ToBeExcluded(Type type)
{
// Arrange
var options = new MvcOptions();
options.ValidationExcludeFilters.Add(type);
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(options);
var activator = new TypeActivator();
var serviceProvider = new Mock<IServiceProvider>();
var provider = new DefaultValidationExcludeFiltersProvider(optionsAccessor.Object,
activator,
serviceProvider.Object);
// Act
var filters = provider.ExcludeFilters;
// Assert
Assert.Equal(1, filters.Count);
Assert.True(filters[0].IsTypeExcluded(type));
}
[Theory]
[InlineData(typeof(BaseType))]
[InlineData(typeof(UnRelatedType))]
[InlineData(typeof(UnRelatedType))]
public void Add_RegisterDerivedType_BaseAndUnrealatedTypesAreNotExcluded(Type type)
{
// Arrange
var options = new MvcOptions();
options.ValidationExcludeFilters.Add(typeof(DerivedType));
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(options);
var activator = new TypeActivator();
var serviceProvider = new Mock<IServiceProvider>();
var provider = new DefaultValidationExcludeFiltersProvider(optionsAccessor.Object,
activator,
serviceProvider.Object);
// Act
var filters = provider.ExcludeFilters;
// Assert
Assert.Equal(1, filters.Count);
Assert.False(filters[0].IsTypeExcluded(type));
}
[Theory]
[InlineData(typeof(BaseType))]
[InlineData(typeof(DerivedType))]
public void Add_WithTypeName_RegistersTypesAndDerivedType_ToBeExcluded(Type type)
{
// Arrange
var options = new MvcOptions();
options.ValidationExcludeFilters.Add(type.FullName);
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(options);
var activator = new TypeActivator();
var serviceProvider = new Mock<IServiceProvider>();
var provider = new DefaultValidationExcludeFiltersProvider(optionsAccessor.Object,
activator,
serviceProvider.Object);
// Act
var filters = provider.ExcludeFilters;
// Assert
Assert.Equal(1, filters.Count);
Assert.True(filters[0].IsTypeExcluded(type));
}
[Theory]
[InlineData(typeof(BaseType))]
[InlineData(typeof(UnRelatedType))]
[InlineData(typeof(SubNameSpace.UnRelatedType))]
public void Add_WithTypeName_RegisterDerivedType_BaseAndUnrealatedTypesAreNotExcluded(Type type)
{
// Arrange
var options = new MvcOptions();
options.ValidationExcludeFilters.Add(typeof(DerivedType).FullName);
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(options);
var activator = new TypeActivator();
var serviceProvider = new Mock<IServiceProvider>();
var provider = new DefaultValidationExcludeFiltersProvider(optionsAccessor.Object,
activator,
serviceProvider.Object);
// Act
var filters = provider.ExcludeFilters;
// Assert
Assert.Equal(1, filters.Count);
Assert.False(filters[0].IsTypeExcluded(type));
}
private class BaseType
{
}
private class DerivedType : BaseType
{
}
private class UnRelatedType
{
}
}
namespace SubNameSpace
{
public class UnRelatedType
{
}
}
}

View File

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.OptionDescriptors
{
public class ExcludeValidationDescriptorTests
{
[Fact]
public void ConstructorThrows_IfTypeIsNotIExcludeTypeFromBodyValidation()
{
// Arrange
var expected = "The type 'System.String' must derive from " +
"'Microsoft.AspNet.Mvc.ModelBinding.IExcludeTypeValidationFilter'.";
var type = typeof(string);
// Act & Assert
ExceptionAssert.ThrowsArgument(() => new ExcludeValidationDescriptor(type), "type", expected);
}
[Fact]
public void ConstructorSetsOptionType()
{
// Arrange
var type = typeof(TestExcludeFilter);
// Act
var descriptor = new ExcludeValidationDescriptor(type);
// Assert
Assert.Equal(type, descriptor.OptionType);
Assert.Null(descriptor.Instance);
}
[Fact]
public void ConstructorSetsInstanceeAndOptionType()
{
// Arrange
var instance = new TestExcludeFilter();
// Act
var descriptor = new ExcludeValidationDescriptor(instance);
// Assert
Assert.Same(instance, descriptor.Instance);
Assert.Equal(instance.GetType(), descriptor.OptionType);
}
private class TestExcludeFilter : IExcludeTypeValidationFilter
{
public bool IsTypeExcluded([NotNull] Type propertyType)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Mvc.OptionDescriptors;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class ValidationExcludeFiltersExtensionsTests
{
[Fact]
public void InputFormatterDescriptors_AddsTypesAndTypeNames()
{
// Arrange
var type1 = typeof(BaseType);
var collection = new List<ExcludeValidationDescriptor>();
// Act
collection.Add(type1);
collection.Add(type1.FullName);
// Assert
Assert.Equal(2, collection.Count);
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), collection[0].OptionType);
Assert.Equal(typeof(DefaultTypeNameBasedExcludeFilter), collection[1].OptionType);
}
private class BaseType
{
}
}
}

View File

@ -2,9 +2,10 @@
// 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.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Xml; using System.Xml.Linq;
namespace Microsoft.AspNet.Mvc.FunctionalTests namespace Microsoft.AspNet.Mvc.FunctionalTests
{ {
@ -12,33 +13,22 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
{ {
public static string RetrieveAntiForgeryToken(string htmlContent, string actionUrl) public static string RetrieveAntiForgeryToken(string htmlContent, string actionUrl)
{ {
int startSearchIndex = 0; htmlContent = "<Root>" + htmlContent + "</Root>";
var reader = new StringReader(htmlContent);
while (startSearchIndex < htmlContent.Length) var htmlDocument = XDocument.Load(reader);
foreach (var form in htmlDocument.Descendants("form"))
{ {
var formStartIndex = htmlContent.IndexOf("<form", startSearchIndex, StringComparison.OrdinalIgnoreCase); foreach (var attribute in form.Attributes())
var formEndIndex = htmlContent.IndexOf("</form>", startSearchIndex, StringComparison.OrdinalIgnoreCase);
if (formStartIndex == -1 || formEndIndex == -1)
{ {
//Unable to find the form start or end - finish the search if (string.Equals(attribute.Name.LocalName, "action", StringComparison.OrdinalIgnoreCase) &&
return null; attribute.Value.EndsWith(actionUrl, StringComparison.OrdinalIgnoreCase))
}
formEndIndex = formEndIndex + "</form>".Length;
startSearchIndex = formEndIndex + 1;
var htmlDocument = new XmlDocument();
htmlDocument.LoadXml(htmlContent.Substring(formStartIndex, formEndIndex - formStartIndex));
foreach (XmlAttribute attribute in htmlDocument.DocumentElement.Attributes)
{
if (string.Equals(attribute.Name, "action", StringComparison.OrdinalIgnoreCase) && attribute.Value.EndsWith(actionUrl, StringComparison.OrdinalIgnoreCase))
{ {
foreach (XmlNode input in htmlDocument.GetElementsByTagName("input")) foreach (var input in form.Descendants("input"))
{ {
if (input.Attributes["name"].Value == "__RequestVerificationToken" && input.Attributes["type"].Value == "hidden") if (input.Attributes("name").First().Value == "__RequestVerificationToken" &&
input.Attributes("type").First().Value == "hidden")
{ {
return input.Attributes["value"].Value; return input.Attributes("value").First().Value;
} }
} }
} }

View File

@ -77,16 +77,31 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Arrange // Arrange
var server = TestServer.Create(_services, _app); var server = TestServer.Create(_services, _app);
var client = server.CreateClient(); var client = server.CreateClient();
var sampleString = "RandomString"; var content = new StringContent("{\"Alias\":\"xyz\"}", Encoding.UTF8, "application/json");
var input = "{ NameThatThrowsOnGet:'" + sampleString + "'}";
var content = new StringContent(input, Encoding.UTF8, "application/json");
// Act // Act
var response = await client.PostAsync("http://localhost/Validation/GetDeveloperName", content); var response = await client.PostAsync("http://localhost/Validation/GetDeveloperName", content);
//Assert //Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Developer's get was not accessed after set.", await response.Content.ReadAsStringAsync()); Assert.Equal("No model validation for developer, even though developer.Name is empty.", await response.Content.ReadAsStringAsync());
}
[Fact]
public async Task CheckIfExcludedField_IsValidatedForNonBodyBoundModels()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var content = new StringContent("{\"Alias\":\"xyz\"}", Encoding.UTF8, "application/json");
// Act
var response = await client.PostAsync("http://localhost/Validation/GetDeveloperAlias", content);
//Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("The Name field is required.", await response.Content.ReadAsStringAsync());
} }
} }
} }

View File

@ -25,7 +25,7 @@
"UrlHelperWebSite": "1.0.0", "UrlHelperWebSite": "1.0.0",
"ValueProvidersSite": "1.0.0", "ValueProvidersSite": "1.0.0",
"VersioningWebSite": "1.0.0", "VersioningWebSite": "1.0.0",
"ViewComponentWebSite": "1.0.0", "ViewComponentWebSite": "1.0.0",
"XmlSerializerWebSite": "1.0.0", "XmlSerializerWebSite": "1.0.0",
"WebApiCompatShimWebSite": "1.0.0", "WebApiCompatShimWebSite": "1.0.0",
"Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.TestHost": "1.0.0-*",

View File

@ -5,7 +5,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq; using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -259,11 +262,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
typeof(Uri), typeof(Uri),
new List<Type>() { typeof(Uri) } new List<Type>() { typeof(Uri) }
}; };
yield return new object[] {
new DerivedUri("/api/values", UriKind.Relative),
typeof(Uri),
new List<Type>() { typeof(Uri) }
};
yield return new object[] { new Dictionary<string, Uri> { yield return new object[] { new Dictionary<string, Uri> {
{ "values", new Uri("/api/values", UriKind.Relative) }, { "values", new Uri("/api/values", UriKind.Relative) },
{ "hello", new Uri("/api/hello", UriKind.Relative) } { "hello", new Uri("/api/hello", UriKind.Relative) }
@ -355,17 +353,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
new DataMemberModelValidatorProvider() new DataMemberModelValidatorProvider()
}); });
var modelMetadataProvider = new EmptyModelMetadataProvider(); var modelMetadataProvider = new EmptyModelMetadataProvider();
List<ExcludeFromValidationDelegate> excludedValidationTypesPredicate = var excludedValidationTypesPredicate =
new List<ExcludeFromValidationDelegate>(); new List<IExcludeTypeValidationFilter>();
if (excludedTypes != null) if (excludedTypes != null)
{ {
excludedValidationTypesPredicate = new List<ExcludeFromValidationDelegate>() var mockExcludeTypeFilter = new Mock<IExcludeTypeValidationFilter>();
{ mockExcludeTypeFilter.Setup(o => o.IsTypeExcluded(It.IsAny<Type>()))
(excludedType) => .Returns<Type>(excludedType =>
{ excludedTypes.Any(t => t.IsAssignableFrom(excludedType)));
return excludedTypes.Any(t => t.IsAssignableFrom(excludedType));
} excludedValidationTypesPredicate.Add(mockExcludeTypeFilter.Object);
};
} }
return new ModelValidationContext( return new ModelValidationContext(
@ -379,7 +376,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
modelType: type, modelType: type,
propertyName: null), propertyName: null),
containerMetadata: null, containerMetadata: null,
excludeFromValidationDelegate: excludedValidationTypesPredicate); excludeFromValidationFilters: excludedValidationTypesPredicate);
} }
public class Person public class Person
@ -482,16 +479,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public Team Test { get; set; } public Team Test { get; set; }
} }
public class DerivedUri : Uri
{
public DerivedUri(string uri, UriKind kind) :base(uri, kind)
{
}
[Required]
public string UriPurpose { get; set; }
}
} }
} }
#endif #endif

View File

@ -1,8 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. 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 System;
using System.Xml;
using System.Xml.Linq;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor;
using Newtonsoft.Json.Linq;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -113,5 +117,36 @@ namespace Microsoft.AspNet.Mvc
Assert.IsType<DataAnnotationsModelValidatorProvider>(mvcOptions.ModelValidatorProviders[0].Instance); Assert.IsType<DataAnnotationsModelValidatorProvider>(mvcOptions.ModelValidatorProviders[0].Instance);
Assert.IsType<DataMemberModelValidatorProvider>(mvcOptions.ModelValidatorProviders[1].Instance); Assert.IsType<DataMemberModelValidatorProvider>(mvcOptions.ModelValidatorProviders[1].Instance);
} }
[Fact]
public void Setup_SetsUpExcludeFromValidationDelegates()
{
// Arrange
var mvcOptions = new MvcOptions();
var setup = new MvcOptionsSetup();
// Act
setup.Configure(mvcOptions);
// Assert
Assert.Equal(5, mvcOptions.ValidationExcludeFilters.Count);
// Verify if the delegates registered by default exclude the given types.
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[0].OptionType);
var instance = Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[0].Instance);
Assert.Equal(instance.ExcludedType, typeof(XObject));
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[1].OptionType);
var instance2 = Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[1].Instance);
Assert.Equal(instance2.ExcludedType, typeof(Type));
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[2].OptionType);
var instance3 = Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[2].Instance);
Assert.Equal(instance3.ExcludedType, typeof(byte[]));
Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[3].OptionType);
var instance4 = Assert.IsType<DefaultTypeBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[3].Instance);
Assert.Equal(instance4.ExcludedType, typeof(JToken));
Assert.Equal(typeof(DefaultTypeNameBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[4].OptionType);
var instance5 = Assert.IsType<DefaultTypeNameBasedExcludeFilter>(mvcOptions.ValidationExcludeFilters[4].Instance);
Assert.Equal(instance5.ExcludedTypeName, "System.Xml.XmlNode");
}
} }
} }

View File

@ -35,6 +35,7 @@
</div> </div>
} }
</section> </section>
</div>
@using (Html.BeginForm("UseFacebookLogin", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) @using (Html.BeginForm("UseFacebookLogin", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{ {
@ -60,4 +61,4 @@
</div> </div>
</div> </div>
} }
</div> </div>

View File

@ -23,16 +23,36 @@ namespace FormatterWebSite
} }
[HttpPost] [HttpPost]
public string GetDeveloperName([FromBody]Developer developer) public string GetDeveloperName([FromBody] Developer developer)
{ {
// Developer is excluded in startup, hence the value should never be passed.
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
return "Developer's get was not accessed after set."; if (string.IsNullOrEmpty(developer.Name))
{
return "No model validation for developer, even though developer.Name is empty.";
}
return developer.Name;
} }
else else
{ {
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
} }
[HttpPost]
public string GetDeveloperAlias(Developer developer)
{
// Since validation exclusion is currently only effective in case of body bound models.
if (ModelState.IsValid)
{
return developer.Alias;
}
else
{
return ModelState["Name"].Errors[0].ErrorMessage;
}
}
} }
} }

View File

@ -8,24 +8,9 @@ namespace FormatterWebSite
{ {
public class Developer public class Developer
{ {
private string _name;
[Required] [Required]
public string NameThatThrowsOnGet public string Name { get; set; }
{
get
{
if (_name == "RandomString")
{
throw new InvalidOperationException();
}
return _name; public string Alias { get; set; }
}
set
{
_name = value;
}
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace FormatterWebSite
services.Configure<MvcOptions>(options => services.Configure<MvcOptions>(options =>
{ {
options.ExcludeFromValidationDelegates.Add(typeof(Developer)); options.ValidationExcludeFilters.Add(typeof(Developer));
}); });
}); });