From a6ce9abab1a4a2af91659ba14a317c030f331d92 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 11 Aug 2015 08:37:32 -0700 Subject: [PATCH] Fix #2776 - Add implicit [BindRequired] for value type properties --- .../ModelBinding/Metadata/BindingMetadata.cs | 6 +- .../Metadata/DefaultModelMetadata.cs | 34 ++++-- ...taMemberRequiredBindingMetadataProvider.cs | 2 +- .../ModelBinding/ArrayModelBinderTest.cs | 14 ++- .../Metadata/DefaultModelMetadataTest.cs | 102 ++++++++++++++++- .../MutableObjectModelBinderTest.cs | 103 +++++++----------- .../ModelMetadataProviderTest.cs | 19 +++- .../HtmlGenerationTest.cs | 5 +- .../ModelBindingFromQueryTest.cs | 8 +- .../ModelBindingTest.cs | 2 +- ...WebSite.HtmlGeneration_Customer.Index.html | 8 +- .../BodyValidationIntegrationTests.cs | 2 +- .../CollectionModelBinderIntegrationTest.cs | 7 +- ...MutableObjectModelBinderIntegrationTest.cs | 7 +- .../ValidationIntegrationTests.cs | 6 +- 15 files changed, 228 insertions(+), 97 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs index e7540642f2..6183b883a7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs @@ -38,9 +38,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// /// Gets or sets a value indicating whether or not the request must contain a value for the model. /// Will be ignored if the model metadata being created does not represent a property. - /// See . + /// See . If null, the value of + /// will be computed based on + /// . /// - public bool IsBindingRequired { get; set; } + public bool? IsBindingRequired { get; set; } /// /// Gets or sets a value indicating whether or not the model is read-only. Will be ignored diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index b5c3d6886f..4a83480299 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata private ReadOnlyDictionary _additionalValues; private ModelMetadata _elementMetadata; private bool _haveCalculatedElementMetadata; + private bool? _isBindingRequired; private bool? _isReadOnly; private bool? _isRequired; private ModelPropertyCollection _properties; @@ -334,14 +335,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { get { - if (MetadataKind == ModelMetadataKind.Property) + if (!_isBindingRequired.HasValue) { - return BindingMetadata.IsBindingRequired; - } - else - { - return false; + if (MetadataKind == ModelMetadataKind.Type) + { + _isBindingRequired = false; + } + else if (BindingMetadata.IsBindingRequired.HasValue) + { + _isBindingRequired = BindingMetadata.IsBindingRequired; + } + else + { + // Default to IsBindingRequired = true for value types. + _isBindingRequired = !AllowsNullValue(ModelType); + } } + + return _isBindingRequired.Value; } } @@ -370,14 +381,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { if (!_isReadOnly.HasValue) { - if (BindingMetadata.IsReadOnly.HasValue) - { - _isReadOnly = BindingMetadata.IsReadOnly; - } - else if (MetadataKind == ModelMetadataKind.Type) + if (MetadataKind == ModelMetadataKind.Type) { _isReadOnly = false; } + else if (BindingMetadata.IsReadOnly.HasValue) + { + _isReadOnly = BindingMetadata.IsReadOnly; + } else { _isReadOnly = _details.PropertySetter == null; @@ -401,6 +412,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } else { + // Default to IsRequired = true for value types. _isRequired = !AllowsNullValue(ModelType); } } diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/DataMemberRequiredBindingMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/DataMemberRequiredBindingMetadataProvider.cs index a9b6884c61..e4eb241ffd 100644 --- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/DataMemberRequiredBindingMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/DataMemberRequiredBindingMetadataProvider.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata return; } - if (context.BindingMetadata.IsBindingRequired) + if (context.BindingMetadata.IsBindingRequired == true) { // This value is already required, no need to look at attributes. return; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs index f05bfbd649..682c309364 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs @@ -211,11 +211,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test bool isReadOnly = false) { var metadataProvider = new TestModelMetadataProvider(); - metadataProvider.ForType().BindingDetails(bd => bd.IsReadOnly = isReadOnly); + metadataProvider.ForProperty( + typeof(ModelWithIntArrayProperty), + nameof(ModelWithIntArrayProperty.ArrayProperty)).BindingDetails(bd => bd.IsReadOnly = isReadOnly); + var modelMetadata = metadataProvider.GetMetadataForProperty( + typeof(ModelWithIntArrayProperty), + nameof(ModelWithIntArrayProperty.ArrayProperty)); var bindingContext = new ModelBindingContext { - ModelMetadata = metadataProvider.GetMetadataForType(typeof(int[])), + ModelMetadata = modelMetadata, ModelName = "someName", ValueProvider = valueProvider, OperationBindingContext = new OperationBindingContext @@ -245,6 +250,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { public string[] ArrayProperty { get; set; } } + + private class ModelWithIntArrayProperty + { + public int[] ArrayProperty { get; set; } + } } } #endif diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs index 0394b8ffe0..f55875ebbc 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs @@ -38,14 +38,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.False(metadata.HasNonDefaultEditFormat); Assert.False(metadata.HideSurroundingHtml); Assert.True(metadata.HtmlEncode); - Assert.False(metadata.IsBindingRequired); + Assert.True(metadata.IsBindingAllowed); + Assert.False(metadata.IsBindingRequired); // Defaults to false for reference types Assert.False(metadata.IsComplexType); Assert.False(metadata.IsCollectionType); Assert.False(metadata.IsEnum); Assert.False(metadata.IsFlagsEnum); Assert.False(metadata.IsNullableValueType); Assert.False(metadata.IsReadOnly); - Assert.False(metadata.IsRequired); + Assert.False(metadata.IsRequired); // Defaults to false for reference types Assert.True(metadata.ShowForDisplay); Assert.True(metadata.ShowForEdit); @@ -172,6 +173,99 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { } + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(int))] + public void IsBindingAllowed_ReturnsTrue_ForTypes(Type modelType) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(modelType); + var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + cache.BindingMetadata = new BindingMetadata() + { + IsBindingAllowed = false, // Will be ignored. + }; + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var isBindingAllowed = metadata.IsBindingAllowed; + + // Assert + Assert.True(isBindingAllowed); + } + + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(int))] + public void IsBindingRequired_ReturnsFalse_ForTypes(Type modelType) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForType(modelType); + var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + cache.BindingMetadata = new BindingMetadata() + { + IsBindingRequired = true, // Will be ignored. + }; + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var isBindingRequired = metadata.IsBindingRequired; + + // Assert + Assert.False(isBindingRequired); + } + + [Theory] + [InlineData(typeof(string))] + [InlineData(typeof(IDisposable))] + [InlineData(typeof(Nullable))] + public void IsBindingRequired_ReturnsFalse_ForNullablePropertyTypes(Type modelType) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForProperty(modelType, "Test", typeof(string)); + var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var isBindingRequired = metadata.IsBindingRequired; + + // Assert + Assert.False(isBindingRequired); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DayOfWeek))] + public void IsBindingRequired_ReturnsTrue_ForNonNullablePropertyTypes(Type modelType) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + var detailsProvider = new EmptyCompositeMetadataDetailsProvider(); + + var key = ModelMetadataIdentity.ForProperty(modelType, "Test", typeof(string)); + var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + + var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); + + // Act + var isBindingRequired = metadata.IsBindingRequired; + + // Assert + Assert.True(isBindingRequired); + } + [Theory] [InlineData(typeof(string))] [InlineData(typeof(IDisposable))] @@ -486,6 +580,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata var key = ModelMetadataIdentity.ForType(typeof(int[])); var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0])); + cache.BindingMetadata = new BindingMetadata() + { + IsReadOnly = true, // Will be ignored. + }; var metadata = new DefaultModelMetadata(provider, detailsProvider, cache); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs index 52469afd41..5fbc79b1f9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs @@ -762,8 +762,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var bindingContext = new ModelBindingContext { - // Any type, even an otherwise-simple POCO with an indexer property, would do here. - ModelMetadata = GetMetadataForType(typeof(List)), + ModelMetadata = GetMetadataForType(typeof(PersonCollection)), OperationBindingContext = new OperationBindingContext { ValidatorProvider = Mock.Of(), @@ -942,16 +941,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } [Fact] - [ReplaceCulture] - public void ProcessDto_MissingDataForRequiredFields_NoErrors() + public void ProcessDto_ValueTypeProperty_WithBindingOptional_NoValueSet_NoError() { // Arrange - var model = new ModelWithRequired(); + var model = new BindingOptionalProperty(); var containerMetadata = GetMetadataForType(model.GetType()); - var bindingContext = CreateContext(containerMetadata, model); - // Set no properties though Age (a non-Nullable struct) and City (a class) properties are required. var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var testableBinder = new TestableMutableObjectModelBinder(); var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model); @@ -962,53 +958,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Assert var modelStateDictionary = bindingContext.ModelState; Assert.True(modelStateDictionary.IsValid); - Assert.Empty(modelStateDictionary); } [Fact] - [ReplaceCulture] - public void ProcessDto_ValueTypeProperty_WithRequiredAttribute_SetToNull_NoError() + public void ProcessDto_NullableValueTypeProperty_NoValueSet_NoError() { // Arrange - var model = new ModelWithRequired(); + var model = new NullableValueTypeProperty(); var containerMetadata = GetMetadataForType(model.GetType()); var bindingContext = CreateContext(containerMetadata, model); - - var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); - var testableBinder = new TestableMutableObjectModelBinder(); - - // Make Age valid and City invalid. - var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "Age"); - dto.Results[propertyMetadata] = new ModelBindingResult( - 23, - isModelSet: true, - key: "theModel.Age"); - - propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "City"); - dto.Results[propertyMetadata] = new ModelBindingResult( - null, - isModelSet: true, - key: "theModel.City"); - var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model); - - // Act - testableBinder.ProcessDto(bindingContext, dto, modelValidationNode); - - // Assert - var modelStateDictionary = bindingContext.ModelState; - Assert.True(modelStateDictionary.IsValid); - Assert.Empty(modelStateDictionary); - } - - [Fact] - public void ProcessDto_PropertyWithRequiredAttribute_NoPropertiesSet_NoError() - { - // Arrange - var model = new Person(); - var containerMetadata = GetMetadataForType(model.GetType()); - var bindingContext = CreateContext(containerMetadata, model); - - // Set no properties though ValueTypeRequired (a non-Nullable struct) property is required. + var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var testableBinder = new TestableMutableObjectModelBinder(); var modelValidationNode = new ModelValidationNode(string.Empty, containerMetadata, model); @@ -1091,14 +1050,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } [Fact] - public void ProcessDto_ValueTypeProperty_NoValue_NoError() + public void ProcessDto_ValueTypeProperty_NoValue_Error() { // Arrange var model = new Person(); var containerMetadata = GetMetadataForType(model.GetType()); var bindingContext = CreateContext(containerMetadata, model); - var modelStateDictionary = bindingContext.ModelState; + var modelState = bindingContext.ModelState; var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var testableBinder = new TestableMutableObjectModelBinder(); @@ -1124,8 +1083,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding testableBinder.ProcessDto(bindingContext, dto, modelValidationNode); // Assert - Assert.True(modelStateDictionary.IsValid); - Assert.Empty(modelStateDictionary); + Assert.False(modelState.IsValid); + + var entry = modelState["theModel." + nameof(Person.ValueTypeRequiredWithDefaultValue)]; + var error = Assert.Single(entry.Errors); + Assert.Equal( + $"A value for the '{nameof(Person.ValueTypeRequiredWithDefaultValue)}' property was not provided.", + error.ErrorMessage); + Assert.Null(error.Exception); } [Fact] @@ -1763,10 +1728,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { } + private class BindingOptionalProperty + { + [BindingBehavior(BindingBehavior.Optional)] + public int ValueTypeRequired { get; set; } + } + + private class NullableValueTypeProperty + { + [BindingBehavior(BindingBehavior.Optional)] + public int? NullableValueType { get; set; } + } + private class Person { private DateTime? _dateOfDeath; + [BindingBehavior(BindingBehavior.Optional)] public DateTime DateOfBirth { get; set; } public DateTime? DateOfDeath @@ -1793,6 +1771,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public string LastName { get; set; } public string NonUpdateableProperty { get; private set; } + [BindingBehavior(BindingBehavior.Optional)] [DefaultValue(typeof(decimal), "123.456")] public decimal PropertyWithDefaultValue { get; set; } @@ -1820,17 +1799,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public string NonUpdateableProperty { get; private set; } } - private class ModelWithRequired - { - public string Name { get; set; } - - [Required] - public int Age { get; set; } - - [Required] - public string City { get; set; } - } - private class ModelWithBindRequired { public string Name { get; set; } @@ -1993,6 +1961,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public string Name { get; set; } } + private class PersonCollection + { + public Person this[int index] + { + get + { + return null; + } + } + } + private class CollectionContainer { public int[] ReadOnlyArray { get; } = new int[4]; diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs index 5ff3946e60..30a9d531ff 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/ModelMetadataProviderTest.cs @@ -346,7 +346,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata // Arrange var attributes = new[] { attribute }; var provider = CreateProvider(attributes); - var metadata = provider.GetMetadataForType(typeof(string)); + var metadata = provider.GetMetadataForProperty(typeof(CoolUser), nameof(CoolUser.Name)); // Act var result = accessor(metadata); @@ -932,6 +932,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } } + public class CoolUser + { + public string Name { get; set; } + } + public class TypeBasedBinderAttribute : Attribute, IModelNameProvider { public string Name { get; set; } @@ -1038,6 +1043,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata key, new ModelAttributes(_attributes.Concat(entry.ModelAttributes.TypeAttributes).ToArray())); } + + protected override DefaultMetadataDetails[] CreatePropertyDetails([NotNull] ModelMetadataIdentity key) + { + var entries = base.CreatePropertyDetails(key); + return entries.Select(e => + { + return new DefaultMetadataDetails( + e.Key, + new ModelAttributes(_attributes.Concat(e.ModelAttributes.PropertyAttributes), e.ModelAttributes.TypeAttributes)); + }) + .ToArray(); + } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs index a5d4990fee..2536f0c926 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -196,11 +196,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Customer/HtmlGeneration_Customer"); var nameValueCollection = new List> { - new KeyValuePair("Number", string.Empty), + new KeyValuePair("Number", "0"), new KeyValuePair("Name", string.Empty), new KeyValuePair("Email", string.Empty), new KeyValuePair("PhoneNumber", string.Empty), - new KeyValuePair("Password", string.Empty) + new KeyValuePair("Password", string.Empty), + new KeyValuePair("Gender", "Female"), }; request.Content = new FormUrlEncodedContent(nameValueCollection); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromQueryTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromQueryTest.cs index dd1496b377..f512f3240e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromQueryTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromQueryTest.cs @@ -122,8 +122,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert var body = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject(body); - var error = Assert.Single(result.ModelStateErrors); - Assert.Equal("TestEmployees[0].EmployeeId", error); + + Assert.Collection( + result.ModelStateErrors, + e => Assert.Equal("TestEmployees[0].EmployeeId", e), + e => Assert.Equal("TestEmployees[0].EmployeeTaxId", e), + e => Assert.Equal("TestEmployees[0].Age", e)); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs index 696f366d9d..af7257a2d5 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs @@ -2218,7 +2218,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var server = TestHelper.CreateServer(_app, SiteName, _configureServices); var client = server.CreateClient(); - var url = "http://localhost/TryUpdateModel/TryUpdateModel_ClearsModelStateEntries"; + var url = "http://localhost/TryUpdateModel/TryUpdateModel_ClearsModelStateEntries?id=5&price=1"; // Act var response = await client.GetAsync(url); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html index 7d6cf27c87..a32dfd7f63 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html @@ -3,8 +3,8 @@
- - The value '' is invalid. + + The field Number must be between 1 and 100.
@@ -27,10 +27,10 @@
Male - Female + Female
-
  • The value '' is invalid.
  • +
    • The field Number must be between 1 and 100.
    • The Password field is required.
    • diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs index 31838694c8..db88f551b7 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs @@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Person4 { [FromBody] - [Required] + [BindingBehavior(BindingBehavior.Optional)] public int Address { get; set; } } diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs index 5eca2f79f7..8301dcd1d4 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs @@ -396,10 +396,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0].Name").Value; Assert.Null(entry.Value); Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + var error = Assert.Single(entry.Errors); + Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Name").Value; Assert.Null(entry.Value); Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + error = Assert.Single(entry.Errors); + Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); } [Fact] @@ -548,6 +552,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Address4 { + [BindingBehavior(BindingBehavior.Optional)] public int Zip { get; set; } public string Street { get; set; } @@ -606,7 +611,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Address5 { - public int Zip { get; set; } + public int? Zip { get; set; } [StringLength(3)] public string Street { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs index eb187ba7dd..07fa780584 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Order1 { + [BindingBehavior(BindingBehavior.Optional)] public int ProductId { get; set; } public Person1 Customer { get; set; } @@ -261,7 +262,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Order2 { - public int ProductId { get; set; } + public int? ProductId { get; set; } public Person2 Customer { get; set; } } @@ -435,6 +436,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Order3 { + [BindingBehavior(BindingBehavior.Optional)] public int ProductId { get; set; } public Person3 Customer { get; set; } @@ -578,7 +580,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Order4 { - public int ProductId { get; set; } + public int? ProductId { get; set; } public Person4 Customer { get; set; } } @@ -1341,6 +1343,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests { public string Name { get; set; } + [BindingBehavior(BindingBehavior.Optional)] public KeyValuePair ProductId { get; set; } } diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs index 013aeb8475..ebde9dae1a 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs @@ -195,7 +195,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Person3 { - public int Age { get; set; } + public int? Age { get; set; } [Required] public string Name { get; set; } @@ -1018,12 +1018,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests private class Address { - public int Street { get; set; } + public int? Street { get; set; } public string State { get; set; } [Range(10000, 99999)] - public int Zip { get; set; } + public int? Zip { get; set; } public Country Country { get; set; } }