From 53379e4395fbd337222c970f57741dafb10fe2a9 Mon Sep 17 00:00:00 2001 From: sornaks Date: Fri, 10 Oct 2014 10:23:46 -0700 Subject: [PATCH] Issue #1206 - DefaultBodyModelValidator throws if get accessor throws. Fix: The MvcOptions takes in a list of ExcludeFromValidationDelegate (Func). This func verifies if the type is excluded in validation or not. --- .../ModelBinders/BodyModelBinder.cs | 14 ++- src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs | 12 ++- ...ExcludeFromValidationDelegateExtensions.cs | 26 ++++++ .../Validation/DefaultBodyModelValidator.cs | 20 ++++- .../ExcludeFromValidationDelegate.cs | 14 +++ .../Validation/ModelValidationContext.cs | 21 +++++ .../project.json | 2 +- .../ApiController.cs | 14 +-- src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs | 11 +++ src/Microsoft.AspNet.Mvc/project.json | 3 +- .../BodyModelBinderTests.cs | 24 +++-- .../InputObjectValidationTests.cs | 18 ++++ .../DefaultBodyModelValidatorTests.cs | 88 +++++++++++++++++-- .../Controllers/ValidationController.cs | 14 +++ .../FormatterWebSite/Models/Developer.cs | 31 +++++++ test/WebSites/FormatterWebSite/Startup.cs | 7 +- 16 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs create mode 100644 test/WebSites/FormatterWebSite/Models/Developer.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs index 0cf768ddf7..374ca38220 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs @@ -8,6 +8,7 @@ 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 { @@ -20,14 +21,17 @@ namespace Microsoft.AspNet.Mvc private readonly ActionContext _actionContext; private readonly IInputFormatterSelector _formatterSelector; private readonly IBodyModelValidator _bodyModelValidator; + private readonly IOptions _mvcOptions; public BodyModelBinder([NotNull] IContextAccessor context, [NotNull] IInputFormatterSelector selector, - [NotNull] IBodyModelValidator bodyModelValidator) + [NotNull] IBodyModelValidator bodyModelValidator, + [NotNull] IOptions mvcOptions) { _actionContext = context.Value; _formatterSelector = selector; _bodyModelValidator = bodyModelValidator; + _mvcOptions = mvcOptions; } protected override async Task BindAsync(ModelBindingContext bindingContext, IBodyBinderMarker marker) @@ -48,7 +52,13 @@ namespace Microsoft.AspNet.Mvc bindingContext.Model = await formatter.ReadAsync(formatterContext); // Validate the deserialized object - var validationContext = new ModelValidationContext(bindingContext, bindingContext.ModelMetadata); + var validationContext = new ModelValidationContext( + bindingContext.MetadataProvider, + bindingContext.ValidatorProvider, + bindingContext.ModelState, + bindingContext.ModelMetadata, + containerMetadata: null, + excludeFromValidationDelegate: _mvcOptions.Options.ExcludeFromValidationDelegates); _bodyModelValidator.Validate(validationContext, bindingContext.ModelName); return true; } diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs index 924c9f66da..2d1db68fe5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptions.cs @@ -3,9 +3,10 @@ using System; using System.Collections.Generic; -using Microsoft.AspNet.Mvc.Core; -using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Mvc.ApplicationModel; +using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.OptionDescriptors; namespace Microsoft.AspNet.Mvc { @@ -69,6 +70,13 @@ 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 + /// + public List ExcludeFromValidationDelegates { get; } + = new List(); + /// /// Gets or sets the maximum number of validation errors that are allowed by this application before further /// errors are ignored. diff --git a/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs new file mode 100644 index 0000000000..6fab7e2cdf --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/OptionDescriptors/ExcludeFromValidationDelegateExtensions.cs @@ -0,0 +1,26 @@ +// 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 excludes the properties of the specified type + /// and and it's derived types from validation. + /// + /// 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.ModelBinding/Validation/DefaultBodyModelValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs index 307bae208f..24fce5935a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs @@ -17,7 +17,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public class DefaultBodyModelValidator : IBodyModelValidator { /// - public bool Validate([NotNull] ModelValidationContext modelValidationContext, string keyPrefix) + public bool Validate( + [NotNull] ModelValidationContext modelValidationContext, + string keyPrefix) { var metadata = modelValidationContext.ModelMetadata; var validationContext = new ValidationContext() @@ -54,7 +56,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // We don't need to recursively traverse the graph for types that shouldn't be validated var modelType = metadata.Model.GetType(); - if (TypeHelper.IsSimpleType(modelType)) + if (TypeHelper.IsSimpleType(modelType) || + IsTypeExcludedFromValidation( + validationContext.ModelValidationContext.ExcludeFromValidationDelegate, modelType)) { return ShallowValidate(metadata, validationContext, validators); } @@ -192,6 +196,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return isValid; } + private bool IsTypeExcludedFromValidation( + IReadOnlyList predicates, Type type) + { + // This can be set to null in ModelBinding scenarios which does not flow through this path. + if (predicates == null) + { + return false; + } + + return predicates.Any(t => t(type)); + } + private static Type GetElementType(Type type) { Contract.Assert(typeof(IEnumerable).IsAssignableFrom(type)); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs new file mode 100644 index 0000000000..ed28bc598c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ExcludeFromValidationDelegate.cs @@ -0,0 +1,14 @@ +// 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 type is excluded from validation. + /// + /// which needs to be checked. + /// if excluded, otherwise. + public delegate bool ExcludeFromValidationDelegate(Type type); +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs index ddda6462e5..1fe066e675 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationContext.cs @@ -1,6 +1,8 @@ // 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; + namespace Microsoft.AspNet.Mvc.ModelBinding { public class ModelValidationContext @@ -20,12 +22,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding [NotNull] ModelStateDictionary modelState, [NotNull] ModelMetadata metadata, ModelMetadata containerMetadata) + : this(metadataProvider, + validatorProvider, + modelState, + metadata, + containerMetadata, + excludeFromValidationDelegate: null) + { + } + + public ModelValidationContext([NotNull] IModelMetadataProvider metadataProvider, + [NotNull] IModelValidatorProvider validatorProvider, + [NotNull] ModelStateDictionary modelState, + [NotNull] ModelMetadata metadata, + ModelMetadata containerMetadata, + IReadOnlyList excludeFromValidationDelegate) { ModelMetadata = metadata; ModelState = modelState; MetadataProvider = metadataProvider; ValidatorProvider = validatorProvider; ContainerMetadata = containerMetadata; + ExcludeFromValidationDelegate = excludeFromValidationDelegate; } public ModelValidationContext([NotNull] ModelValidationContext parentContext, @@ -36,6 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelState = parentContext.ModelState; MetadataProvider = parentContext.MetadataProvider; ValidatorProvider = parentContext.ValidatorProvider; + ExcludeFromValidationDelegate = parentContext.ExcludeFromValidationDelegate; } public ModelMetadata ModelMetadata { get; private set; } @@ -47,5 +66,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public IModelMetadataProvider MetadataProvider { get; private set; } public IModelValidatorProvider ValidatorProvider { get; private set; } + + public IReadOnlyList ExcludeFromValidationDelegate { get; private set; } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/project.json b/src/Microsoft.AspNet.Mvc.ModelBinding/project.json index 664d517169..b4ea80a369 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/project.json +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/project.json @@ -8,7 +8,7 @@ "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, "Microsoft.DataAnnotations": "1.0.0-*", "Microsoft.Framework.DependencyInjection": "1.0.0-*", - "Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*", + "Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*", "Newtonsoft.Json": "6.0.4" }, "frameworks": { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index c7b1d22c1f..db3b82fc9f 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs @@ -8,6 +8,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; namespace System.Web.Http { @@ -107,15 +108,18 @@ namespace System.Web.Http /// public void Validate(TEntity entity, string keyPrefix) { + var mvcOptions = Context.RequestServices.GetRequiredService>(); var validator = Context.RequestServices.GetRequiredService(); var metadataProvider = Context.RequestServices.GetRequiredService(); var modelMetadata = metadataProvider.GetMetadataForType(() => entity, typeof(TEntity)); var validatorProvider = Context.RequestServices.GetRequiredService(); - var modelValidationContext = new ModelValidationContext(metadataProvider, - validatorProvider, - ModelState, - modelMetadata, - containerMetadata: null); + var modelValidationContext = new ModelValidationContext( + metadataProvider, + validatorProvider, + ModelState, + modelMetadata, + containerMetadata: null, + excludeFromValidationDelegate: mvcOptions.Options.ExcludeFromValidationDelegates); validator.Validate(modelValidationContext, keyPrefix); } diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs index 7b5c72cb82..cf69451c24 100644 --- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs @@ -1,9 +1,13 @@ // 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 Microsoft.Framework.OptionsModel; +using Newtonsoft.Json.Linq; namespace Microsoft.AspNet.Mvc { @@ -52,6 +56,13 @@ namespace Microsoft.AspNet.Mvc // Set up validators options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider()); 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)); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/project.json b/src/Microsoft.AspNet.Mvc/project.json index 696831c208..bef8b704a1 100644 --- a/src/Microsoft.AspNet.Mvc/project.json +++ b/src/Microsoft.AspNet.Mvc/project.json @@ -25,7 +25,8 @@ "System.Linq": "4.0.0-beta-*", "System.Reflection": "4.0.10-beta-*", "System.Runtime": "4.0.20-beta-*", - "System.Runtime.Extensions": "4.0.10-beta-*" + "System.Runtime.Extensions": "4.0.10-beta-*", + "System.Xml.XmlDocument": "4.0.0-beta-*" } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs index c6cd9ee3f6..b8ebcfc17a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.PipelineCore; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; using Moq; using Xunit; @@ -31,8 +32,8 @@ namespace Microsoft.AspNet.Mvc var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object); bindingContext.ModelMetadata.Marker = Mock.Of(); - - var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object); + + var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object, null); // Act var binderResult = await binder.BindModelAsync(bindingContext); @@ -87,7 +88,7 @@ namespace Microsoft.AspNet.Mvc ModelMetadata = metadataProvider.GetMetadataForType(null, modelType), ModelName = "someName", ValueProvider = Mock.Of(), - ModelBinder = GetBodyBinder(inputFormatter, null), + ModelBinder = GetBodyBinder(inputFormatter, null, null), MetadataProvider = metadataProvider, HttpContext = new DefaultHttpContext(), ModelState = new ModelStateDictionary() @@ -96,7 +97,8 @@ namespace Microsoft.AspNet.Mvc return bindingContext; } - private static BodyModelBinder GetBodyBinder(IInputFormatter inputFormatter, IBodyModelValidator validator) + private static BodyModelBinder GetBodyBinder( + IInputFormatter inputFormatter, IBodyModelValidator validator, IOptions mvcOptions) { var actionContext = CreateActionContext(new DefaultHttpContext()); var inputFormatterSelector = new Mock(); @@ -111,9 +113,19 @@ 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 binder = new BodyModelBinder(actionContext, - inputFormatterSelector.Object, - validator); + inputFormatterSelector.Object, + validator, + mvcOptions); return binder; } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs index 79bf67fb32..4e6c24f298 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -70,5 +70,23 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "The field Designation must match the regular expression '[0-9a-zA-Z]*'.", await response.Content.ReadAsStringAsync()); } + + [Fact] + public async Task CheckIfExcludedFieldsAreNotValidated() + { + // 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"); + + // 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()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs index d948c2f3d7..7100422baa 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs @@ -9,7 +9,6 @@ using System.Linq; using Microsoft.AspNet.Testing; using Moq; using Xunit; -using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -235,8 +234,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } - // This case should be handled in a better way. - // Issue - https://github.com/aspnet/Mvc/issues/1206 tracks this. [Fact] [ReplaceCulture] public void BodyValidator_Throws_IfPropertyAccessorThrows() @@ -253,6 +250,62 @@ namespace Microsoft.AspNet.Mvc.ModelBinding }); } + public static IEnumerable ObjectsWithPropertiesWhichThrowOnGet + { + get + { + yield return new object[] { + new Uri("/api/values", UriKind.Relative), + 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) } + }, typeof(Uri), new List() { typeof(Uri) } }; + } + } + + [Theory] + [MemberData(nameof(ObjectsWithPropertiesWhichThrowOnGet))] + [ReplaceCulture] + public void BodyValidator_DoesNotThrow_IfExcludedPropertyAccessorsThrow( + object input, Type type, List excludedTypes) + { + // Arrange + var validationContext = GetModelValidationContext(input, type, excludedTypes); + + // Act & Assert + Assert.DoesNotThrow( + () => + { + new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); + }); + Assert.True(validationContext.ModelState.IsValid); + } + + [Fact] + [ReplaceCulture] + public void BodyValidator_Throws_IfPropertyGetterThrows() + { + // Arrange + var validationContext = GetModelValidationContext( + new Uri("/api/values", UriKind.Relative), typeof(Uri), new List()); + + // Act & Assert + Assert.Throws( + () => + { + new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); + }); + Assert.True(validationContext.ModelState.IsValid); + } + [Fact] [ReplaceCulture] public void MultipleValidationErrorsOnSameMemberReported() @@ -290,7 +343,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding () => new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty)); } - private ModelValidationContext GetModelValidationContext(object model, Type type) + private ModelValidationContext GetModelValidationContext( + object model, Type type, List excludedTypes = null) { var modelStateDictionary = new ModelStateDictionary(); var provider = new Mock(); @@ -301,6 +355,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding new DataMemberModelValidatorProvider() }); var modelMetadataProvider = new EmptyModelMetadataProvider(); + List excludedValidationTypesPredicate = + new List(); + if (excludedTypes != null) + { + excludedValidationTypesPredicate = new List() + { + (excludedType) => + { + return excludedTypes.Any(t => t.IsAssignableFrom(excludedType)); + } + }; + } + return new ModelValidationContext( modelMetadataProvider, new CompositeModelValidatorProvider(provider.Object), @@ -311,7 +378,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding modelAccessor: () => model, modelType: type, propertyName: null), - containerMetadata: null); + containerMetadata: null, + excludeFromValidationDelegate: excludedValidationTypesPredicate); } public class Person @@ -414,6 +482,16 @@ 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/WebSites/FormatterWebSite/Controllers/ValidationController.cs b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs index d3a3fe4ce6..e65fb6a95c 100644 --- a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs @@ -1,6 +1,7 @@ // 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; namespace FormatterWebSite @@ -20,5 +21,18 @@ namespace FormatterWebSite return Content("User has been registerd : " + user.Name); } + + [HttpPost] + public string GetDeveloperName([FromBody]Developer developer) + { + if (ModelState.IsValid) + { + return "Developer's get was not accessed after set."; + } + else + { + throw new InvalidOperationException(); + } + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/Developer.cs b/test/WebSites/FormatterWebSite/Models/Developer.cs new file mode 100644 index 0000000000..7a02b2a882 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/Developer.cs @@ -0,0 +1,31 @@ +// 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.ComponentModel.DataAnnotations; + +namespace FormatterWebSite +{ + public class Developer + { + private string _name; + + [Required] + public string NameThatThrowsOnGet + { + get + { + if (_name == "RandomString") + { + throw new InvalidOperationException(); + } + + return _name; + } + set + { + _name = value; + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Startup.cs b/test/WebSites/FormatterWebSite/Startup.cs index 502301c7fb..3cd5627c1d 100644 --- a/test/WebSites/FormatterWebSite/Startup.cs +++ b/test/WebSites/FormatterWebSite/Startup.cs @@ -2,6 +2,7 @@ // 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.Mvc; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; @@ -18,6 +19,11 @@ namespace FormatterWebSite { // Add MVC services to the services container services.AddMvc(configuration); + + services.Configure(options => + { + options.ExcludeFromValidationDelegates.Add(typeof(Developer)); + }); }); // Add MVC to the request pipeline @@ -25,7 +31,6 @@ namespace FormatterWebSite { routes.MapRoute("ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); - }); } }