diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultValidationExcludeFiltersProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultValidationExcludeFiltersProvider.cs new file mode 100644 index 0000000000..ddffe2eb20 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultValidationExcludeFiltersProvider.cs @@ -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 +{ + /// + public class DefaultValidationExcludeFiltersProvider + : OptionDescriptorBasedProvider, IValidationExcludeFiltersProvider + { + /// + /// Initializes a new instance of the DefaultBodyValidationExcludeFiltersProvider class. + /// + /// An accessor to the configured for this application. + /// An instance used to instantiate types. + /// A instance that retrieves services from the + /// service collection. + public DefaultValidationExcludeFiltersProvider(IOptions optionsAccessor, + ITypeActivator typeActivator, + IServiceProvider serviceProvider) + : base(optionsAccessor.Options.ValidationExcludeFilters, typeActivator, serviceProvider) + { + } + + /// + public IReadOnlyList ExcludeFilters + { + get + { + return Options; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultTypeBasedExcludeFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultTypeBasedExcludeFilter.cs new file mode 100644 index 0000000000..2926cd546f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultTypeBasedExcludeFilter.cs @@ -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 +{ + /// + /// Provides an implementation of which can filter + /// based on a type. + /// + public class DefaultTypeBasedExcludeFilter : IExcludeTypeValidationFilter + { + /// + /// Creates a new instance of . + /// + /// The type which needs to be excluded. + public DefaultTypeBasedExcludeFilter([NotNull] Type type) + { + ExcludedType = type; + } + + /// + /// Gets the type which is excluded from validation. + /// + public Type ExcludedType { get; } + + /// + public bool IsTypeExcluded([NotNull] Type propertyType) + { + return ExcludedType.IsAssignableFrom(propertyType); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultTypeNameBasedExcludeFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultTypeNameBasedExcludeFilter.cs new file mode 100644 index 0000000000..89c45c2bab --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/DefaultTypeNameBasedExcludeFilter.cs @@ -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 +{ + /// + /// Provides an implementation of which can filter + /// based on a type full name. + /// + public class DefaultTypeNameBasedExcludeFilter : IExcludeTypeValidationFilter + { + /// + /// Creates a new instance of + /// + /// Fully qualified name of the type which needs to be excluded. + public DefaultTypeNameBasedExcludeFilter([NotNull] string typeFullName) + { + ExcludedTypeName = typeFullName; + } + + /// + /// Gets the type full name which is excluded from validation. + /// + public string ExcludedTypeName { get; } + + /// + 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()); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/IValidationExcludeFiltersProvider.cs b/src/Microsoft.AspNet.Mvc.Core/IValidationExcludeFiltersProvider.cs new file mode 100644 index 0000000000..3e6e14fec5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/IValidationExcludeFiltersProvider.cs @@ -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 +{ + /// + /// Provides an activated collection of instances. + /// + public interface IValidationExcludeFiltersProvider + { + /// + /// Gets a collection of activated instances. + /// + IReadOnlyList ExcludeFilters { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs index b971bc3c40..b7dc556819 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs @@ -1,14 +1,10 @@ // 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 System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc { @@ -21,17 +17,17 @@ namespace Microsoft.AspNet.Mvc private readonly ActionContext _actionContext; private readonly IInputFormatterSelector _formatterSelector; private readonly IBodyModelValidator _bodyModelValidator; - private readonly IOptions _mvcOptions; + private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider; public BodyModelBinder([NotNull] IContextAccessor context, [NotNull] IInputFormatterSelector selector, [NotNull] IBodyModelValidator bodyModelValidator, - [NotNull] IOptions mvcOptions) + [NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider) { _actionContext = context.Value; _formatterSelector = selector; _bodyModelValidator = bodyModelValidator; - _mvcOptions = mvcOptions; + _bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider; } protected override async Task BindAsync( @@ -60,7 +56,7 @@ namespace Microsoft.AspNet.Mvc bindingContext.ModelState, bindingContext.ModelMetadata, containerMetadata: null, - excludeFromValidationDelegate: _mvcOptions.Options.ExcludeFromValidationDelegates); + excludeFromValidationFilters: _bodyValidationExcludeFiltersProvider.ExcludeFilters); _bodyModelValidator.Validate(validationContext, bindingContext.ModelName); return true; } diff --git a/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs deleted file mode 100644 index 7aa96d6435..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs +++ /dev/null @@ -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 -{ - /// - /// Extensions for . - /// - public static class ExcludeFromValidationDelegateExtensions - { - /// - /// Adds a delegate to the specified - /// that exludes the properties of the specified and it's derived types from validaton. - /// - /// of . - /// which should be excluded from validation. - public static void Add(this IList list, Type type) - { - list.Add(t => t.IsAssignableFrom(type)); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeValidationDescriptor.cs b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeValidationDescriptor.cs new file mode 100644 index 0000000000..356bce425e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeValidationDescriptor.cs @@ -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 +{ + /// + /// Encapsulates information that describes an . + /// + public class ExcludeValidationDescriptor : OptionDescriptor + { + /// + /// Creates a new instance of . + /// + /// + /// A type that the descriptor represents. + /// + public ExcludeValidationDescriptor([NotNull] Type type) + : base(type) + { + } + + /// + /// Creates a new instance of . + /// + /// An instance of + /// that the descriptor represents. + public ExcludeValidationDescriptor([NotNull] IExcludeTypeValidationFilter validationFilter) + : base(validationFilter) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ValidationExcludeFiltersExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ValidationExcludeFiltersExtensions.cs new file mode 100644 index 0000000000..ff644186ea --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ValidationExcludeFiltersExtensions.cs @@ -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 +{ + /// + /// Extensions for . + /// + public static class ValidationExcludeFiltersExtensions + { + /// + /// Adds a descriptor to the specified + /// that excludes the properties of the specified and it's derived types from validaton. + /// + /// A list of + /// which are used to get a collection of exclude filters to be applied for filtering model properties during validation. + /// + /// which should be excluded from validation. + public static void Add(this IList excludeBodyValidationDescriptorCollection, + Type type) + { + var typeBasedExcludeFilter = new DefaultTypeBasedExcludeFilter(type); + excludeBodyValidationDescriptorCollection.Add(new ExcludeValidationDescriptor(typeBasedExcludeFilter)); + } + + /// + /// Adds a descriptor to the specified + /// that excludes the properties of the type specified and it's derived types from validaton. + /// + /// A list of + /// which are used to get a collection of exclude filters to be applied for filtering model properties during validation. + /// + /// Full name of the type which should be excluded from validation. + public static void Add(this IList excludeBodyValidationDescriptorCollection, + string typeFullName) + { + var filter = new DefaultTypeNameBasedExcludeFilter(typeFullName); + excludeBodyValidationDescriptorCollection.Add(new ExcludeValidationDescriptor(filter)); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/MvcOptions.cs similarity index 93% rename from src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs rename to src/Microsoft.AspNet.Mvc.Core/Rendering/MvcOptions.cs index d84ffc2f1c..e282c86e8f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/MvcOptions.cs @@ -71,11 +71,11 @@ namespace Microsoft.AspNet.Mvc public List InputFormatters { get; private set; } /// - /// Gets a list of which return whether the given type - /// should be excluded from Validation in + /// Gets a list of which are used to construct a list + /// of exclude filters by , /// - public List ExcludeFromValidationDelegates { get; } - = new List(); + public List ValidationExcludeFilters { get; } + = new List(); /// /// Gets or sets the maximum number of validation errors that are allowed by this application before further diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs index 24fce5935a..41cf7fe32a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var modelType = metadata.Model.GetType(); if (TypeHelper.IsSimpleType(modelType) || IsTypeExcludedFromValidation( - validationContext.ModelValidationContext.ExcludeFromValidationDelegate, modelType)) + validationContext.ModelValidationContext.ExcludeFromValidationFilters, modelType)) { return ShallowValidate(metadata, validationContext, validators); } @@ -197,15 +197,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } private bool IsTypeExcludedFromValidation( - IReadOnlyList predicates, Type type) + IReadOnlyList filters, Type type) { // 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 predicates.Any(t => t(type)); + return filters.Any(filter => filter.IsTypeExcluded(type)); } private static Type GetElementType(Type type) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs deleted file mode 100644 index e91cd3acd5..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs +++ /dev/null @@ -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 -{ - /// - /// Delegate that determines if the specified is excluded from validation. - /// - /// which needs to be checked. - /// true if excluded, false otherwise. - public delegate bool ExcludeFromValidationDelegate(Type type); -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IExcludeTypeValidationFilter.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IExcludeTypeValidationFilter.cs new file mode 100644 index 0000000000..31b99d184b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/IExcludeTypeValidationFilter.cs @@ -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 +{ + /// + /// Provides an interface which is used to determine if s are excluded from model validation. + /// + public interface IExcludeTypeValidationFilter + { + /// + /// Determines if the given type is excluded from validation. + /// + /// The for which the check is to be performed. + /// True if the type is to be excluded. False otherwise. + bool IsTypeExcluded([NotNull] Type type); + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs index 1fe066e675..cbcb4916d6 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding modelState, metadata, containerMetadata, - excludeFromValidationDelegate: null) + excludeFromValidationFilters: null) { } @@ -36,14 +36,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding [NotNull] ModelStateDictionary modelState, [NotNull] ModelMetadata metadata, ModelMetadata containerMetadata, - IReadOnlyList excludeFromValidationDelegate) + IReadOnlyList excludeFromValidationFilters) { ModelMetadata = metadata; ModelState = modelState; MetadataProvider = metadataProvider; ValidatorProvider = validatorProvider; ContainerMetadata = containerMetadata; - ExcludeFromValidationDelegate = excludeFromValidationDelegate; + ExcludeFromValidationFilters = excludeFromValidationFilters; } public ModelValidationContext([NotNull] ModelValidationContext parentContext, @@ -54,19 +54,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelState = parentContext.ModelState; MetadataProvider = parentContext.MetadataProvider; 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 { get; private set; } + public IReadOnlyList ExcludeFromValidationFilters { get; } } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index da9598f990..e6298979ce 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs @@ -12,7 +12,6 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.OptionsModel; using Newtonsoft.Json; using MvcMediaTypeHeaderValue = Microsoft.AspNet.Mvc.HeaderValueAbstractions.MediaTypeHeaderValue; @@ -402,7 +401,8 @@ namespace System.Web.Http /// public void Validate(TEntity entity, string keyPrefix) { - var mvcOptions = Context.RequestServices.GetRequiredService>(); + var bodyValidationExcludeFiltersProvider = Context.RequestServices + .GetRequiredService(); var validator = Context.RequestServices.GetRequiredService(); var metadataProvider = Context.RequestServices.GetRequiredService(); var modelMetadata = metadataProvider.GetMetadataForType(() => entity, typeof(TEntity)); @@ -413,7 +413,7 @@ namespace System.Web.Http ModelState, modelMetadata, containerMetadata: null, - excludeFromValidationDelegate: mvcOptions.Options.ExcludeFromValidationDelegates); + excludeFromValidationFilters: bodyValidationExcludeFiltersProvider.ExcludeFilters); validator.Validate(modelValidationContext, keyPrefix); } diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index cf69451c24..6b25295798 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -58,11 +58,11 @@ namespace Microsoft.AspNet.Mvc options.ModelValidatorProviders.Add(new DataMemberModelValidatorProvider()); // Add types to be excluded from Validation - options.ExcludeFromValidationDelegates.Add(typeof(XmlNode)); - options.ExcludeFromValidationDelegates.Add(typeof(XObject)); - options.ExcludeFromValidationDelegates.Add(typeof(Type)); - options.ExcludeFromValidationDelegates.Add(typeof(byte[])); - options.ExcludeFromValidationDelegates.Add(typeof(JToken)); + options.ValidationExcludeFilters.Add(typeof(XObject)); + options.ValidationExcludeFilters.Add(typeof(Type)); + options.ValidationExcludeFilters.Add(typeof(byte[])); + options.ValidationExcludeFilters.Add(typeof(JToken)); + options.ValidationExcludeFilters.Add(typeFullName: "System.Xml.XmlNode"); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 5ff07e2edc..9b47c012b5 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -99,6 +99,7 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); yield return describe.Scoped(); yield return describe.Transient(); + yield return describe.Transient(); // // Razor, Views and runtime compilation diff --git a/src/Microsoft.AspNet.Mvc/project.json b/src/Microsoft.AspNet.Mvc/project.json index dc62fa5ebc..3d1b2ac692 100644 --- a/src/Microsoft.AspNet.Mvc/project.json +++ b/src/Microsoft.AspNet.Mvc/project.json @@ -10,10 +10,6 @@ }, "frameworks": { "aspnet50": {}, - "aspnetcore50": { - "dependencies": { - "System.Xml.XmlDocument": "4.0.0-beta-*" - } - } + "aspnetcore50": {} } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs index caa52ec240..75b4830e9f 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNet.Mvc var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object); bindingContext.ModelMetadata.BinderMetadata = Mock.Of(); - var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object, null); + var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object); // Act var binderResult = await binder.BindModelAsync(bindingContext); @@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc ModelMetadata = metadataProvider.GetMetadataForType(null, modelType), ModelName = "someName", ValueProvider = Mock.Of(), - ModelBinder = GetBodyBinder(inputFormatter, null, null), + ModelBinder = GetBodyBinder(inputFormatter, null), MetadataProvider = metadataProvider, HttpContext = new DefaultHttpContext(), ModelState = new ModelStateDictionary() @@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc } private static BodyModelBinder GetBodyBinder( - IInputFormatter inputFormatter, IBodyModelValidator validator, IOptions mvcOptions) + IInputFormatter inputFormatter, IBodyModelValidator validator) { var actionContext = CreateActionContext(new DefaultHttpContext()); var inputFormatterSelector = new Mock(); @@ -113,19 +113,14 @@ namespace Microsoft.AspNet.Mvc validator = mockValidator.Object; } - if (mvcOptions == null) - { - var options = new Mock(); - options.CallBase = true; - var mockMvcOptions = new Mock>(); - mockMvcOptions.SetupGet(o => o.Options).Returns(options.Object); - mvcOptions = mockMvcOptions.Object; - } + var bodyValidationPredicatesProvidwer = new Mock(); + bodyValidationPredicatesProvidwer.SetupGet(o => o.ExcludeFilters) + .Returns(new List()); var binder = new BodyModelBinder(actionContext, - inputFormatterSelector.Object, - validator, - mvcOptions); + inputFormatterSelector.Object, + validator, + bodyValidationPredicatesProvidwer.Object); return binder; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/DefaultValidationExcludeFiltersProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/DefaultValidationExcludeFiltersProviderTests.cs new file mode 100644 index 0000000000..1bd1c20f62 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/DefaultValidationExcludeFiltersProviderTests.cs @@ -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>(); + optionsAccessor.SetupGet(o => o.Options) + .Returns(options); + var activator = new TypeActivator(); + var serviceProvider = new Mock(); + 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>(); + optionsAccessor.SetupGet(o => o.Options) + .Returns(options); + var activator = new TypeActivator(); + var serviceProvider = new Mock(); + 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>(); + optionsAccessor.SetupGet(o => o.Options) + .Returns(options); + var activator = new TypeActivator(); + var serviceProvider = new Mock(); + 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>(); + optionsAccessor.SetupGet(o => o.Options) + .Returns(options); + var activator = new TypeActivator(); + var serviceProvider = new Mock(); + 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 + { + } + } +} + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/ExcludeValidationDescriptorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/ExcludeValidationDescriptorTests.cs new file mode 100644 index 0000000000..f0aa5ed315 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/ExcludeValidationDescriptorTests.cs @@ -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(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/ValidationExcludeFiltersExtensionsTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/ValidationExcludeFiltersExtensionsTests.cs new file mode 100644 index 0000000000..65b9fe3ee9 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/OptionDescriptors/ValidationExcludeFiltersExtensionsTests.cs @@ -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(); + + // 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 + { + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs index 7a78b99a79..3db1da5913 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs @@ -2,9 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Linq; using System.Net.Http; -using System.Xml; +using System.Xml.Linq; namespace Microsoft.AspNet.Mvc.FunctionalTests { @@ -12,33 +13,22 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { public static string RetrieveAntiForgeryToken(string htmlContent, string actionUrl) { - int startSearchIndex = 0; - - while (startSearchIndex < htmlContent.Length) + htmlContent = "" + htmlContent + ""; + var reader = new StringReader(htmlContent); + var htmlDocument = XDocument.Load(reader); + foreach (var form in htmlDocument.Descendants("form")) { - var formStartIndex = htmlContent.IndexOf("", startSearchIndex, StringComparison.OrdinalIgnoreCase); - - if (formStartIndex == -1 || formEndIndex == -1) + foreach (var attribute in form.Attributes()) { - //Unable to find the form start or end - finish the search - return null; - } - - formEndIndex = formEndIndex + "".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)) + if (string.Equals(attribute.Name.LocalName, "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; } } } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs index 4e6c24f298..2291ba9081 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -77,16 +77,31 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - var sampleString = "RandomString"; - var input = "{ NameThatThrowsOnGet:'" + sampleString + "'}"; - var content = new StringContent(input, Encoding.UTF8, "application/json"); + var content = new StringContent("{\"Alias\":\"xyz\"}", Encoding.UTF8, "application/json"); // Act var response = await client.PostAsync("http://localhost/Validation/GetDeveloperName", content); //Assert 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()); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index b382301089..71059af8ee 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -25,7 +25,7 @@ "UrlHelperWebSite": "1.0.0", "ValueProvidersSite": "1.0.0", "VersioningWebSite": "1.0.0", - "ViewComponentWebSite": "1.0.0", + "ViewComponentWebSite": "1.0.0", "XmlSerializerWebSite": "1.0.0", "WebApiCompatShimWebSite": "1.0.0", "Microsoft.AspNet.TestHost": "1.0.0-*", diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs index 7100422baa..26358303b2 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs @@ -5,7 +5,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.IO; using System.Linq; +using System.Xml; +using System.Xml.Linq; using Microsoft.AspNet.Testing; using Moq; using Xunit; @@ -259,11 +262,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding typeof(Uri), new List() { typeof(Uri) } }; - yield return new object[] { - new DerivedUri("/api/values", UriKind.Relative), - typeof(Uri), - new List() { typeof(Uri) } - }; yield return new object[] { new Dictionary { { "values", new Uri("/api/values", UriKind.Relative) }, { "hello", new Uri("/api/hello", UriKind.Relative) } @@ -355,17 +353,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new DataMemberModelValidatorProvider() }); var modelMetadataProvider = new EmptyModelMetadataProvider(); - List excludedValidationTypesPredicate = - new List(); + var excludedValidationTypesPredicate = + new List(); if (excludedTypes != null) { - excludedValidationTypesPredicate = new List() - { - (excludedType) => - { - return excludedTypes.Any(t => t.IsAssignableFrom(excludedType)); - } - }; + var mockExcludeTypeFilter = new Mock(); + mockExcludeTypeFilter.Setup(o => o.IsTypeExcluded(It.IsAny())) + .Returns(excludedType => + excludedTypes.Any(t => t.IsAssignableFrom(excludedType))); + + excludedValidationTypesPredicate.Add(mockExcludeTypeFilter.Object); } return new ModelValidationContext( @@ -379,7 +376,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding modelType: type, propertyName: null), containerMetadata: null, - excludeFromValidationDelegate: excludedValidationTypesPredicate); + excludeFromValidationFilters: excludedValidationTypesPredicate); } public class Person @@ -482,16 +479,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding 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 \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs index db8211309b..246f98a625 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs @@ -1,8 +1,12 @@ // 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.Xml; +using System.Xml.Linq; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Razor; +using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.AspNet.Mvc @@ -113,5 +117,36 @@ namespace Microsoft.AspNet.Mvc Assert.IsType(mvcOptions.ModelValidatorProviders[0].Instance); Assert.IsType(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(mvcOptions.ValidationExcludeFilters[0].Instance); + Assert.Equal(instance.ExcludedType, typeof(XObject)); + Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[1].OptionType); + var instance2 = Assert.IsType(mvcOptions.ValidationExcludeFilters[1].Instance); + Assert.Equal(instance2.ExcludedType, typeof(Type)); + Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[2].OptionType); + var instance3 = Assert.IsType(mvcOptions.ValidationExcludeFilters[2].Instance); + Assert.Equal(instance3.ExcludedType, typeof(byte[])); + Assert.Equal(typeof(DefaultTypeBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[3].OptionType); + var instance4 = Assert.IsType(mvcOptions.ValidationExcludeFilters[3].Instance); + Assert.Equal(instance4.ExcludedType, typeof(JToken)); + Assert.Equal(typeof(DefaultTypeNameBasedExcludeFilter), mvcOptions.ValidationExcludeFilters[4].OptionType); + var instance5 = Assert.IsType(mvcOptions.ValidationExcludeFilters[4].Instance); + Assert.Equal(instance5.ExcludedTypeName, "System.Xml.XmlNode"); + } } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Views/Account/Login.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Account/Login.cshtml index 995d6e6867..d285f5f771 100644 --- a/test/WebSites/AntiForgeryWebSite/Views/Account/Login.cshtml +++ b/test/WebSites/AntiForgeryWebSite/Views/Account/Login.cshtml @@ -35,6 +35,7 @@ } + @using (Html.BeginForm("UseFacebookLogin", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @@ -60,4 +61,4 @@ } - + diff --git a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs index e65fb6a95c..1ad8e0e0e2 100644 --- a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs @@ -23,16 +23,36 @@ namespace FormatterWebSite } [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) { - 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 { 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; + } + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/Developer.cs b/test/WebSites/FormatterWebSite/Models/Developer.cs index 7a02b2a882..c26dfce70d 100644 --- a/test/WebSites/FormatterWebSite/Models/Developer.cs +++ b/test/WebSites/FormatterWebSite/Models/Developer.cs @@ -8,24 +8,9 @@ namespace FormatterWebSite { public class Developer { - private string _name; - [Required] - public string NameThatThrowsOnGet - { - get - { - if (_name == "RandomString") - { - throw new InvalidOperationException(); - } + public string Name { get; set; } - return _name; - } - set - { - _name = value; - } - } + public string Alias { get; set; } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Startup.cs b/test/WebSites/FormatterWebSite/Startup.cs index 3cd5627c1d..b70f5f4c6b 100644 --- a/test/WebSites/FormatterWebSite/Startup.cs +++ b/test/WebSites/FormatterWebSite/Startup.cs @@ -22,7 +22,7 @@ namespace FormatterWebSite services.Configure(options => { - options.ExcludeFromValidationDelegates.Add(typeof(Developer)); + options.ValidationExcludeFilters.Add(typeof(Developer)); }); });