diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs index e7775f01bb..97a80555c9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/BodyModelBinder.cs @@ -66,6 +66,8 @@ protected async override Task BindModelCoreAsync( return new ModelBindingResult(modelBindingKey); } + var valueProviderResult = new ValueProviderResult(model, attemptedValue: null, culture: null); + bindingContext.ModelState.SetModelValue(modelBindingKey, valueProviderResult); var validationNode = new ModelValidationNode(modelBindingKey, bindingContext.ModelMetadata, model) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ByteArrayModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ByteArrayModelBinder.cs index 8c8a6af4fd..17cd3d5845 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ByteArrayModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ByteArrayModelBinder.cs @@ -21,17 +21,17 @@ public async Task BindModelAsync([NotNull] ModelBindingConte return null; } - var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName); - // Check for missing data case 1: There was no element containing this data. + var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName); if (valueProviderResult == null) { return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false); } - var value = valueProviderResult.AttemptedValue; + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); // Check for missing data case 2: There was an element but it was left blank. + var value = valueProviderResult.AttemptedValue; if (string.IsNullOrEmpty(value)) { return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false); diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormCollectionModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormCollectionModelBinder.cs index 4c37b27c40..a7be8bcf6f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormCollectionModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormCollectionModelBinder.cs @@ -25,7 +25,7 @@ public async Task BindModelAsync([NotNull] ModelBindingConte return null; } - object model = null; + object model; var request = bindingContext.OperationBindingContext.HttpContext.Request; if (request.HasFormContentType) { @@ -36,8 +36,7 @@ public async Task BindModelAsync([NotNull] ModelBindingConte } else { - var formValuesLookup = form.ToDictionary(p => p.Key, - p => p.Value); + var formValuesLookup = form.ToDictionary(p => p.Key, p => p.Value); model = new FormCollection(formValuesLookup, form.Files); } } @@ -46,6 +45,9 @@ public async Task BindModelAsync([NotNull] ModelBindingConte model = new FormCollection(new Dictionary()); } + var valueProviderResult = new ValueProviderResult(model, attemptedValue: null, culture: null); + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + var validationNode = new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, model); return new ModelBindingResult( diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormFileModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormFileModelBinder.cs index e11821619d..63ea4e3c2a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormFileModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/FormFileModelBinder.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +#if DNXCORE50 using System.Reflection; +#endif using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.Framework.Internal; @@ -20,25 +22,33 @@ public class FormFileModelBinder : IModelBinder /// public async Task BindModelAsync([NotNull] ModelBindingContext bindingContext) { + var typeMatched = false; + object value = null; if (bindingContext.ModelType == typeof(IFormFile)) { + typeMatched = true; var postedFiles = await GetFormFilesAsync(bindingContext); - var value = postedFiles.FirstOrDefault(); - var validationNode = - new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value); - return new ModelBindingResult( - value, - bindingContext.ModelName, - isModelSet: value != null, - validationNode: validationNode); + value = postedFiles.FirstOrDefault(); } - else if (typeof(IEnumerable).GetTypeInfo().IsAssignableFrom( - bindingContext.ModelType.GetTypeInfo())) + else if (typeof(IEnumerable).IsAssignableFrom(bindingContext.ModelType)) { + typeMatched = true; var postedFiles = await GetFormFilesAsync(bindingContext); - var value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles); - var validationNode = - new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value); + value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles); + } + + if (typeMatched) + { + ModelValidationNode validationNode = null; + if (value != null) + { + validationNode = + new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value); + + var valueProviderResult = new ValueProviderResult(value, attemptedValue: null, culture: null); + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + } + return new ModelBindingResult( value, bindingContext.ModelName, diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/HeaderModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/HeaderModelBinder.cs index f44fc94e7d..875f52120c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/HeaderModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/HeaderModelBinder.cs @@ -59,6 +59,10 @@ protected override Task BindModelCoreAsync([NotNull] ModelBi bindingContext.ModelName, bindingContext.ModelMetadata, model); + + var attemptedValue = (model as string) ?? request.Headers.Get(headerName); + var valueProviderResult = new ValueProviderResult(model, attemptedValue, culture: null); + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); } return Task.FromResult( diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs index 449a4281cd..646520d35a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/BodyModelBinderTests.cs @@ -75,7 +75,7 @@ public async Task BindModel_NoInputFormatterFound_SetsModelStateError() // Assert - // Returns true because it understands the metadata type. + // Returns non-null because it understands the metadata type. Assert.NotNull(binderResult); Assert.True(binderResult.IsFatalError); Assert.False(binderResult.IsModelSet); @@ -171,7 +171,7 @@ public async Task CustomFormatterDeserializationException_AddedToModelState() // Assert - // Returns true because it understands the metadata type. + // Returns non-null because it understands the metadata type. Assert.NotNull(binderResult); Assert.True(binderResult.IsFatalError); Assert.False(binderResult.IsModelSet); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ByteArrayModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ByteArrayModelBinderTests.cs index 2a631b32e2..939314bcb3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ByteArrayModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ByteArrayModelBinderTests.cs @@ -34,7 +34,10 @@ public async Task BindModelSetsModelToNullOnNullOrEmptyString(string value) Assert.Equal("foo", binderResult.Key); Assert.Null(binderResult.Model); - Assert.Empty(bindingContext.ModelState); // No submitted value for "foo". + var modelState = Assert.Single(bindingContext.ModelState); + Assert.Equal("foo", modelState.Key); + Assert.NotNull(modelState.Value.Value); + Assert.Equal(value, modelState.Value.Value.RawValue); } [Fact] @@ -64,7 +67,7 @@ public async Task BindModelAddsModelErrorsOnInvalidCharacters() // Arrange var expected = TestPlatformHelper.IsMono ? "Invalid length." : - "The supplied value is invalid for foo."; + "The value '\"Fys1\"' is not valid for foo."; var valueProvider = new SimpleHttpValueProvider() { diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs index 458de1dfe0..7d9ef65634 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs @@ -3,12 +3,9 @@ using System.ComponentModel.DataAnnotations; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.Framework.DependencyInjection; using Xunit; namespace Microsoft.AspNet.Mvc.IntegrationTests @@ -71,10 +68,10 @@ public async Task FromBodyOnActionParameter_EmptyBody_BindsToNullValue() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", BindingSource = BindingSource.Body @@ -99,8 +96,11 @@ public async Task FromBodyOnActionParameter_EmptyBody_BindsToNullValue() Assert.NotNull(modelBindingResult); Assert.True(modelBindingResult.IsModelSet); Assert.Null(modelBindingResult.Model); - Assert.Empty(modelState.Keys); + Assert.True(modelState.IsValid); + var entry = Assert.Single(modelState); + Assert.Empty(entry.Key); + Assert.Null(entry.Value.Value.RawValue); } private class Person4 @@ -115,10 +115,10 @@ public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_AddsModelStat { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", }, @@ -141,13 +141,11 @@ public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_AddsModelStat Assert.NotNull(modelBindingResult); Assert.True(modelBindingResult.IsModelSet); var boundPerson = Assert.IsType(modelBindingResult.Model); - Assert.NotNull(boundPerson); - Assert.False(modelState.IsValid); - // The error with an empty key is a bug(#2416) in our implementation which does not append the prefix and - // use that along with the path. The expected key here would be CustomParameter.Address. - var key = Assert.Single(modelState.Keys, k => k == ""); - var error = Assert.Single(modelState[""].Errors); + Assert.False(modelState.IsValid); + var entry = Assert.Single(modelState); + Assert.Equal(string.Empty, entry.Key); + var error = Assert.Single(entry.Value.Errors); Assert.StartsWith( "No JSON content found and type 'System.Int32' is not nullable.", error.Exception.Message); @@ -167,16 +165,16 @@ private class Address2 public int Zip { get; set; } } - [Theory(Skip = "There should be entries for all model properties which are bound. #2445")] + [Theory] [InlineData("{ \"Zip\" : 123 }")] [InlineData("{}")] public async Task FromBodyOnTopLevelProperty_RequiredOnSubProperty_AddsModelStateError(string inputText) { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", }, @@ -200,14 +198,15 @@ public async Task FromBodyOnTopLevelProperty_RequiredOnSubProperty_AddsModelStat Assert.True(modelBindingResult.IsModelSet); var boundPerson = Assert.IsType(modelBindingResult.Model); Assert.NotNull(boundPerson); + Assert.False(modelState.IsValid); Assert.Equal(2, modelState.Keys.Count); - var zip = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip"); - Assert.Equal(ModelValidationState.Valid, modelState[zip].ValidationState); + var address = Assert.Single(modelState, kvp => kvp.Key == "CustomParameter.Address").Value; + Assert.Equal(ModelValidationState.Unvalidated, address.ValidationState); - var street = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street"); - Assert.Equal(ModelValidationState.Invalid, modelState[street].ValidationState); - var error = Assert.Single(modelState[street].Errors); + var street = Assert.Single(modelState, kvp => kvp.Key == "CustomParameter.Address.Street").Value; + Assert.Equal(ModelValidationState.Invalid, street.ValidationState); + var error = Assert.Single(street.Errors); Assert.Equal("The Street field is required.", error.ErrorMessage); } @@ -225,16 +224,16 @@ private class Address3 public int Zip { get; set; } } - [Theory(Skip = "There should be entries for all model properties which are bound. #2445")] + [Theory] [InlineData("{ \"Street\" : \"someStreet\" }")] [InlineData("{}")] public async Task FromBodyOnProperty_RequiredOnValueTypeSubProperty_AddsModelStateError(string inputText) { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", }, @@ -255,20 +254,11 @@ public async Task FromBodyOnProperty_RequiredOnValueTypeSubProperty_AddsModelSta // Assert Assert.NotNull(modelBindingResult); Assert.True(modelBindingResult.IsModelSet); - var boundPerson = Assert.IsType(modelBindingResult.Model); - Assert.NotNull(boundPerson); - Assert.False(modelState.IsValid); - var street = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street"); - Assert.Equal(ModelValidationState.Valid, modelState[street].ValidationState); - - // The error with an empty key is a bug(#2416) in our implementation which does not append the prefix and - // use that along with the path. The expected key here would be Address. - var zip = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip"); - Assert.Equal(ModelValidationState.Valid, modelState[zip].ValidationState); - var error = Assert.Single(modelState[""].Errors); - Assert.StartsWith( - "Required property 'Zip' not found in JSON. Path ''", - error.Exception.Message); + Assert.IsType(modelBindingResult.Model); + + Assert.True(modelState.IsValid); + Assert.Equal(1, modelState.Count); + Assert.Single(modelState, kvp => kvp.Key == "CustomParameter.Address"); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs index 08173274df..79ca9e9651 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs @@ -16,7 +16,7 @@ private class Person public byte[] Token { get; set; } } - [Theory(Skip = "ModelState.Value not set due to #2445")] + [Theory] [InlineData(true)] [InlineData(false)] public async Task BindProperty_WithData_GetsBound(bool fallBackScenario) @@ -61,19 +61,12 @@ public async Task BindProperty_WithData_GetsBound(bool fallBackScenario) // ModelState Assert.True(modelState.IsValid); - Assert.Equal(2, modelState.Keys.Count); - Assert.Single(modelState.Keys, k => k == prefix); - Assert.Single(modelState.Keys, k => k == queryStringKey); - - var key = Assert.Single(modelState.Keys, k => k == queryStringKey + "[0]"); - Assert.NotNull(modelState[key].Value); // should be non null bug #2445. - Assert.Empty(modelState[key].Errors); - Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); - - key = Assert.Single(modelState.Keys, k => k == queryStringKey + "[1]"); - Assert.NotNull(modelState[key].Value); // should be non null bug #2445. - Assert.Empty(modelState[key].Errors); - Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); + var entry = Assert.Single(modelState); + Assert.Equal(queryStringKey, entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Equal(value, entry.Value.Value.AttemptedValue); + Assert.Equal(value, entry.Value.Value.RawValue); } [Fact] @@ -109,19 +102,18 @@ public async Task BindParameter_NoData_DoesNotGetBound() Assert.Empty(modelState.Keys); } - [Fact(Skip = "ModelState.Value not set due to #2445")] + [Fact] public async Task BindParameter_WithData_GetsBound() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", }, - ParameterType = typeof(byte[]) }; @@ -151,16 +143,11 @@ public async Task BindParameter_WithData_GetsBound() // ModelState Assert.True(modelState.IsValid); - - Assert.Equal(3, modelState.Count); - Assert.Single(modelState.Keys, k => k == "CustomParameter[0]"); - Assert.Single(modelState.Keys, k => k == "CustomParameter[1]"); - var key = Assert.Single(modelState.Keys, k => k == "CustomParameter[2]"); - - Assert.NotNull(modelState[key].Value); - Assert.Equal(value, modelState[key].Value.AttemptedValue); - Assert.Equal(expectedValue, modelState[key].Value.RawValue); - Assert.Empty(modelState[key].Errors); + var entry = Assert.Single(modelState); + Assert.Equal("CustomParameter", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(value, entry.Value.Value.AttemptedValue); + Assert.Equal(value, entry.Value.Value.RawValue); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs index 40cc235f06..8d72bdf1e4 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs @@ -27,12 +27,12 @@ private class Address public FormCollection FileCollection { get; set; } } - [Fact(Skip = "ModelState.Value not set due to #2445")] + [Fact] public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", BindingInfo = new BindingInfo(), @@ -70,11 +70,12 @@ public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound() // ModelState Assert.True(modelState.IsValid); Assert.Equal(2, modelState.Count); - Assert.Single(modelState.Keys, k => k == "Address.Zip"); - var key = Assert.Single(modelState.Keys, k => k == "Address.File"); - Assert.NotNull(modelState[key].Value); // should be non null bug #2445. - Assert.Empty(modelState[key].Errors); - Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); + Assert.Single(modelState, kvp => kvp.Key == "Address.Zip"); + var entry = Assert.Single(modelState, kvp => kvp.Key == "Address.FileCollection").Value; + Assert.Empty(entry.Errors); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + Assert.Null(entry.Value.AttemptedValue); + Assert.Equal(formCollection, entry.Value.RawValue); } [Fact] @@ -82,15 +83,14 @@ public async Task BindParameter_WithData_GetsBound() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { // Setting a custom parameter prevents it from falling back to an empty prefix. BinderModelName = "CustomParameter", }, - ParameterType = typeof(FormCollection) }; @@ -113,7 +113,6 @@ public async Task BindParameter_WithData_GetsBound() // Model var formCollection = Assert.IsType(modelBindingResult.Model); - Assert.NotNull(formCollection); var file = Assert.Single(formCollection.Files); Assert.NotNull(file); Assert.Equal("form-data; name=CustomParameter; filename=text.txt", file.ContentDisposition); @@ -122,10 +121,12 @@ public async Task BindParameter_WithData_GetsBound() // ModelState Assert.True(modelState.IsValid); - - // Validation should be skipped because we do not validate any parameters and since IFormFile is not - // IValidatableObject, we should have no entries in the model state dictionary. - Assert.Empty(modelState.Keys); + var entry = Assert.Single(modelState); + Assert.Equal("CustomParameter", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Null(entry.Value.Value.AttemptedValue); + Assert.Equal(formCollection, entry.Value.Value.RawValue); } [Fact] @@ -133,21 +134,18 @@ public async Task BindParameter_NoData_BindsWithEmptyCollection() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", }, - ParameterType = typeof(FormCollection) }; // No data is passed. - var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => - { - }); + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(); var modelState = new ModelStateDictionary(); @@ -162,7 +160,12 @@ public async Task BindParameter_NoData_BindsWithEmptyCollection() // ModelState Assert.True(modelState.IsValid); - Assert.Empty(modelState.Keys); + var entry = Assert.Single(modelState); + Assert.Equal("CustomParameter", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Null(entry.Value.Value.AttemptedValue); + Assert.Equal(collection, entry.Value.Value.RawValue); // FormCollection Assert.Empty(collection); diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs index e5059d91bd..f73456d4a7 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs @@ -27,7 +27,7 @@ private class Address public IFormFile File { get; set; } } - [Fact(Skip = "ModelState.Value not set due to #2445")] + [Fact] public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound() { // Arrange @@ -71,7 +71,7 @@ public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound() Assert.Equal(2, modelState.Count); Assert.Single(modelState.Keys, k => k == "Address.Zip"); var key = Assert.Single(modelState.Keys, k => k == "Address.File"); - Assert.NotNull(modelState[key].Value); // should be non null bug #2445. + Assert.NotNull(modelState[key].Value); Assert.Empty(modelState[key].Errors); Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); } @@ -81,15 +81,14 @@ public async Task BindParameter_WithData_GetsBound() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { // Setting a custom parameter prevents it from falling back to an empty prefix. BinderModelName = "CustomParameter", }, - ParameterType = typeof(IFormFile) }; @@ -119,10 +118,12 @@ public async Task BindParameter_WithData_GetsBound() // ModelState Assert.True(modelState.IsValid); - - // Validation should be skipped because we do not validate any parameters and since IFormFile is not - // IValidatableObject, we should have no entries in the model state dictionary. - Assert.Empty(modelState.Keys); + var entry = Assert.Single(modelState); + Assert.Equal("CustomParameter", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Null(entry.Value.Value.AttemptedValue); + Assert.Equal(file, entry.Value.Value.RawValue); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs index b1a026d06e..0e4f2c7866 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs @@ -18,8 +18,8 @@ public class GenericModelBinderIntegrationTest // This isn't an especially useful scenario - but it exercises what happens when you // try to use a Collection of something that is bound greedily by model-type. // - // In this example we choose IFormCollection - because IFormCollection has a dedicated - // model binder. + // In this example we choose IFormCollection because IFormCollection has a dedicated + // model binder. [Fact] public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_WithPrefix_Success() { @@ -47,12 +47,18 @@ public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelB Assert.True(modelBindingResult.IsModelSet); var model = Assert.IsType>(modelBindingResult.Model); - Assert.Equal(1, model.Count); - Assert.NotNull(model[0]); + var formCollection = Assert.Single(model); + Assert.NotNull(formCollection); - Assert.Equal(0, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState); + Assert.Equal("parameter[10]", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Null(entry.Value.Value.AttemptedValue); + Assert.Equal(formCollection, entry.Value.Value.RawValue); } // This isn't an especially useful scenario - but it exercises what happens when you @@ -86,12 +92,18 @@ public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelB Assert.True(modelBindingResult.IsModelSet); var model = Assert.IsType>(modelBindingResult.Model); - Assert.Equal(1, model.Count); - Assert.NotNull(model[0]); + var formCollection = Assert.Single(model); + Assert.NotNull(formCollection); - Assert.Equal(0, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState); + Assert.Equal("[10]", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Null(entry.Value.Value.AttemptedValue); + Assert.Equal(formCollection, entry.Value.Value.RawValue); } // This isn't an especially useful scenario - but it exercises what happens when you @@ -217,7 +229,7 @@ public async Task GenericModelBinder_BindsArrayOfDictionary_WithPrefix_Success() ParameterType = typeof(Dictionary[]) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?parameter[0][0].Key=key0¶meter[0][0].Value=10"); }); @@ -263,7 +275,7 @@ public async Task GenericModelBinder_BindsArrayOfDictionary_EmptyPrefix_Success( ParameterType = typeof(Dictionary[]) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?[0][0].Key=key0&[0][0].Value=10"); }); @@ -309,7 +321,7 @@ public async Task GenericModelBinder_BindsArrayOfDictionary_NoData() ParameterType = typeof(Dictionary[]) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?"); }); @@ -345,7 +357,7 @@ public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_WithPrefix_Su ParameterType = typeof(ICollection>) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value=10"); }); @@ -390,7 +402,7 @@ public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_EmptyPrefix_S ParameterType = typeof(ICollection>) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?[0].Key=key0&[0].Value=10"); }); @@ -435,7 +447,7 @@ public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_NoData() ParameterType = typeof(Collection>) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?"); }); @@ -471,7 +483,7 @@ public async Task GenericModelBinder_BindsDictionaryOfList_WithPrefix_Success() ParameterType = typeof(Dictionary>) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString( "?parameter[0].Key=key0¶meter[0].Value[0]=10¶meter[0].Value[1]=11"); @@ -521,7 +533,7 @@ public async Task GenericModelBinder_BindsDictionaryOfList_EmptyPrefix_Success() ParameterType = typeof(Dictionary>) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?[0].Key=key0&[0].Value[0]=10&[0].Value[1]=11"); }); @@ -570,7 +582,7 @@ public async Task GenericModelBinder_BindsDictionaryOfList_NoData() ParameterType = typeof(Dictionary>) }; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) => + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?"); }); diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs index 3d92e4a5e0..70be1379f8 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs @@ -65,7 +65,7 @@ public async Task BindPropertyFromHeader_NoData_UsesFullPathAsKeyForModelStateEr Assert.Equal("The Street field is required.", error.ErrorMessage); } - [Fact(Skip = "ModelState.Value not set due to #2445")] + [Fact] public async Task BindPropertyFromHeader_WithPrefix_GetsBound() { // Arrange @@ -104,19 +104,17 @@ public async Task BindPropertyFromHeader_WithPrefix_GetsBound() // ModelState Assert.True(modelState.IsValid); - Assert.Equal(3, modelState.Count); - Assert.Single(modelState.Keys, k => k == "prefix.Address"); - Assert.Single(modelState.Keys, k => k == "prefix"); - - var key = Assert.Single(modelState.Keys, k => k == "prefix.Address.Header"); - Assert.NotNull(modelState[key].Value); - Assert.Equal("someValue", modelState[key].Value.RawValue); - Assert.Equal("someValue", modelState[key].Value.AttemptedValue); + var entry = Assert.Single(modelState); + Assert.Equal("prefix.Address.Header", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Equal("someValue", entry.Value.Value.AttemptedValue); + Assert.Equal("someValue", entry.Value.Value.RawValue); } // The scenario is interesting as we to bind the top level model we fallback to empty prefix, // and hence the model state keys have an empty prefix. - [Fact(Skip = "ModelState.Value not set due to #2445.")] + [Fact] public async Task BindPropertyFromHeader_WithData_WithEmptyPrefix_GetsBound() { // Arrange @@ -128,11 +126,8 @@ public async Task BindPropertyFromHeader_WithData_WithEmptyPrefix_GetsBound() ParameterType = typeof(Person) }; - // Do not add any headers. - var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { - request.Headers.Add("Header", new[] { "someValue" }); - }); - + var operationContext = ModelBindingTestHelper.GetOperationBindingContext( + request => request.Headers.Add("Header", new[] { "someValue" })); var modelState = new ModelStateDictionary(); // Act @@ -152,27 +147,29 @@ public async Task BindPropertyFromHeader_WithData_WithEmptyPrefix_GetsBound() // ModelState Assert.True(modelState.IsValid); - Assert.Equal(2, modelState.Count); - Assert.Single(modelState.Keys, k => k == "Address"); - var key = Assert.Single(modelState.Keys, k => k == "Address.Header"); - Assert.NotNull(modelState[key].Value); - Assert.Equal("someValue", modelState[key].Value.RawValue); - Assert.Equal("someValue", modelState[key].Value.AttemptedValue); + var entry = Assert.Single(modelState); + Assert.Equal("Address.Header", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Equal("someValue", entry.Value.Value.AttemptedValue); + Assert.Equal("someValue", entry.Value.Value.RawValue); } - [Theory(Skip = "Greedy Model Binders should add a value in model state #2445.")] + [Theory] [InlineData(typeof(string[]), "value1, value2, value3")] [InlineData(typeof(string), "value")] public async Task BindParameterFromHeader_WithData_WithPrefix_ModelGetsBound(Type modelType, string value) { // Arrange - var expectedValue = value.Split(',').Select(v => v.Trim()); + var expectedValue = modelType == typeof(string) ? + (object) value : + (object)value.Split(',').Select(v => v.Trim()).ToArray(); var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "Parameter1", - BindingInfo = new BindingInfo() + BindingInfo = new BindingInfo { BinderModelName = "CustomParameter", BindingSource = BindingSource.Header @@ -180,7 +177,7 @@ public async Task BindParameterFromHeader_WithData_WithPrefix_ModelGetsBound(Typ ParameterType = modelType }; - Action action = (r) => r.Headers.Add("CustomParameter", new[] { value }); + Action action = r => r.Headers.Add("CustomParameter", new[] { value }); var operationContext = ModelBindingTestHelper.GetOperationBindingContext(action); // Do not add any headers. @@ -202,12 +199,12 @@ public async Task BindParameterFromHeader_WithData_WithPrefix_ModelGetsBound(Typ // ModelState Assert.True(modelState.IsValid); - var key = Assert.Single(modelState.Keys); - Assert.Equal("CustomParameter", key); - - Assert.NotNull(modelState[key].Value); - Assert.Equal(expectedValue, modelState[key].Value.RawValue); - Assert.Equal(value, modelState[key].Value.AttemptedValue); + var entry = Assert.Single(modelState); + Assert.Equal("CustomParameter", entry.Key); + Assert.Empty(entry.Value.Errors); + Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState); + Assert.Equal(value, entry.Value.Value.AttemptedValue); + Assert.Equal(expectedValue, entry.Value.Value.RawValue); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs index 363b1c5009..eb187ba7dd 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs @@ -44,7 +44,7 @@ private class Address1 public string Street { get; set; } } - [Fact(Skip = "ModelState.Value not set due to #2445.")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_Success() { // Arrange @@ -85,13 +85,12 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_W Assert.Equal("bill", entry.Value.AttemptedValue); Assert.Equal("bill", entry.Value.RawValue); - // These fail due to #2445 entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Address").Value; Assert.Null(entry.Value.AttemptedValue); // ModelState entries for body don't include original text. Assert.Same(model.Customer.Address, entry.Value.RawValue); } - [Fact(Skip = "ModelState.Value not set due to #2445.")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithEmptyPrefix_Success() { // Arrange @@ -132,7 +131,6 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_W Assert.Equal("bill", entry.Value.AttemptedValue); Assert.Equal("bill", entry.Value.RawValue); - // These fail due to #2445 entry = Assert.Single(modelState, e => e.Key == "Customer.Address").Value; Assert.Null(entry.Value.AttemptedValue); // ModelState entries for body don't include original text. Assert.Same(model.Customer.Address, entry.Value.RawValue); @@ -170,11 +168,14 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_W Assert.Equal("bill", model.Customer.Name); Assert.Null(model.Customer.Address); - Assert.Equal(1, modelState.Count); + Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); - var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value; + var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Address").Value; + Assert.Null(entry.Value.AttemptedValue); + Assert.Null(entry.Value.RawValue); + entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value; Assert.Equal("bill", entry.Value.AttemptedValue); Assert.Equal("bill", entry.Value.RawValue); } @@ -446,7 +447,7 @@ private class Person3 public byte[] Token { get; set; } } - [Fact(Skip = "Greedy model binders should set value. #2445")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithPrefix_Success() { // Arrange @@ -478,7 +479,7 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBin Assert.Equal("bill", model.Customer.Name); Assert.Equal(ByteArrayContent, model.Customer.Token); - Assert.Equal(2, modelState.Count); // This fails due to #2445 + Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); @@ -486,13 +487,12 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBin Assert.Equal("bill", entry.Value.AttemptedValue); Assert.Equal("bill", entry.Value.RawValue); - // These fail due to #2445 entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Token").Value; Assert.Equal(ByteArrayEncoded, entry.Value.AttemptedValue); Assert.Equal(ByteArrayEncoded, entry.Value.RawValue); } - [Fact(Skip = "Greedy model binders should set value. #2445")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithEmptyPrefix_Success() { // Arrange @@ -544,17 +544,13 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBin var parameter = new ParameterDescriptor() { Name = "parameter", - ParameterType = typeof(Order1) + ParameterType = typeof(Order3) }; // Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements. var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?parameter.Customer.Name=bill"); - - // This is set so that the input formatter does not add an error to model state. - // Thus this prevents addition of an extra error unrelated to the test scenario. - request.ContentType = "application/json"; }); var modelState = new ModelStateDictionary(); @@ -566,10 +562,10 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBin Assert.NotNull(modelBindingResult); Assert.True(modelBindingResult.IsModelSet); - var model = Assert.IsType(modelBindingResult.Model); + var model = Assert.IsType(modelBindingResult.Model); Assert.NotNull(model.Customer); Assert.Equal("bill", model.Customer.Name); - Assert.Null(model.Customer.Address); + Assert.Null(model.Customer.Token); Assert.Equal(1, modelState.Count); Assert.Equal(0, modelState.ErrorCount); @@ -594,7 +590,7 @@ private class Person4 public IEnumerable Documents { get; set; } } - [Fact(Skip = "Greedy model binders should set value. #2445")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_Success() { // Arrange @@ -626,7 +622,7 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBind Assert.Equal("bill", model.Customer.Name); Assert.Single(model.Customer.Documents); - Assert.Equal(2, modelState.Count); // This fails due to #2445 + Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); @@ -639,7 +635,7 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBind Assert.Same(model.Customer.Documents, entry.Value.RawValue); } - [Fact(Skip = "Greedy model binders should set value. #2445")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithEmptyPrefix_Success() { // Arrange @@ -717,11 +713,16 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBind Assert.Equal("bill", model.Customer.Name); Assert.Empty(model.Customer.Documents); - Assert.Equal(1, modelState.Count); + Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); - var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value; + var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Documents").Value; + Assert.Null(entry.Value.AttemptedValue); + var documents = Assert.IsAssignableFrom>(entry.Value.RawValue); + Assert.Empty(documents); + + entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value; Assert.Equal("bill", entry.Value.AttemptedValue); Assert.Equal("bill", entry.Value.RawValue); } @@ -1529,7 +1530,7 @@ private class Person9 // If a nested POCO object has all properties bound from a greedy source, then it should be populated // if the top-level object is created. - [Fact(Skip = "ModelState.Value not set due to #2445.")] + [Fact] public async Task MutableObjectModelBinder_BindsNestedPOCO_WithAllGreedyBoundProperties() { // Arrange @@ -1567,7 +1568,7 @@ public async Task MutableObjectModelBinder_BindsNestedPOCO_WithAllGreedyBoundPro Assert.True(modelState.IsValid); var entry = Assert.Single(modelState, e => e.Key == "Customer.Address").Value; - Assert.Null(entry.Value.AttemptedValue); // This fails due to #2445 + Assert.Null(entry.Value.AttemptedValue); Assert.Same(model.Customer.Address, entry.Value.RawValue); } diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs index dfccd8d93b..0c5dc8a502 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs @@ -31,7 +31,7 @@ public async Task Validation_RequiredAttribute_OnSimpleTypeProperty_WithData() Name = "parameter", ParameterType = typeof(Order1) }; - + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?parameter.CustomerName=bill"); @@ -1020,6 +1020,7 @@ private class Order11 private class Address { public int Street { get; set; } + public string State { get; set; } [Range(10000, 99999)] @@ -1032,30 +1033,32 @@ private class Country { public string Name { get; set; } } + [Fact] public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels() { // Arrange - var parameter = new ParameterDescriptor() + var parameter = new ParameterDescriptor { Name = "parameter", ParameterType = typeof(Order11) }; MvcOptions testOptions = null; - var input = "{\"OfficeAddress.Zip\":\"45\"}"; - var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => - { - request.QueryString = - new QueryString("?HomeAddress.Country.Name=US&ShippingAddresses[0].Zip=45&HomeAddress.Zip=46"); - request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input)); - request.ContentType = "application/json"; - }, - options => { - - options.ValidationExcludeFilters.Add(typeof(Address)); - testOptions = options; - }); + var input = "{\"Zip\":\"47\"}"; + var operationContext = ModelBindingTestHelper.GetOperationBindingContext( + request => + { + request.QueryString = + new QueryString("?HomeAddress.Country.Name=US&ShippingAddresses[0].Zip=45&HomeAddress.Zip=46"); + request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input)); + request.ContentType = "application/json"; + }, + options => + { + options.ValidationExcludeFilters.Add(typeof(Address)); + testOptions = options; + }); var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(testOptions); var modelState = new ModelStateDictionary(); @@ -1063,7 +1066,7 @@ public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels() // Act var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); - Assert.Equal(3, modelState.Count); + Assert.Equal(4, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); @@ -1081,6 +1084,14 @@ public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels() Assert.Equal("46", entry.Value.AttemptedValue); Assert.Equal("46", entry.Value.RawValue); Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "OfficeAddress").Value; + Assert.Null(entry.Value.AttemptedValue); + var address = Assert.IsType
(entry.Value.RawValue); + Assert.Equal(47, address.Zip); + + // Address itself is not excluded from validation. + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); } private static void AssertRequiredError(string key, ModelError error)