diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs index 174f632c9b..aa02af55ed 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; @@ -338,5 +337,204 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); } + + private class Person2 + { + public int Id { get; set; } + + [BindRequired] + public string Name { get; set; } + } + + [Fact] + public async Task CollectionModelBinder_BindsListOfComplexType_WithRequiredProperty_WithPrefix_PartialData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(List) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter[0].Id=10¶meter[1].Id=11"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Equal(10, model[0].Id); + Assert.Equal(11, model[1].Id); + Assert.Null(model[0].Name); + Assert.Null(model[1].Name); + + Assert.Equal(4, modelState.Count); + Assert.Equal(2, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0].Id").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Id").Value; + Assert.Equal("11", entry.Value.AttemptedValue); + Assert.Equal("11", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0].Name").Value; + Assert.Null(entry.Value); + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Name").Value; + Assert.Null(entry.Value); + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + } + + [Fact] + public async Task CollectionModelBinder_BindsListOfComplexType_WithRequiredProperty_WithExplicitPrefix_PartialData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix", + }, + ParameterType = typeof(List) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?prefix[0].Id=10&prefix[1].Id=11"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Equal(10, model[0].Id); + Assert.Null(model[0].Name); + Assert.Equal(11, model[1].Id); + Assert.Null(model[1].Name); + + Assert.Equal(4, modelState.Count); + Assert.Equal(2, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0].Id").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[1].Id").Value; + Assert.Equal("11", entry.Value.AttemptedValue); + Assert.Equal("11", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0].Name").Value; + Assert.Null(entry.Value); + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + + entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[1].Name").Value; + Assert.Null(entry.Value); + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + } + + [Fact] + public async Task CollectionModelBinder_BindsCollectionOfComplexType_WithRequiredProperty_EmptyPrefix_PartialData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(ICollection) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?[0].Id=10&[1].Id=11"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Equal(10, model[0].Id); + Assert.Null(model[0].Name); + Assert.Equal(11, model[1].Id); + Assert.Null(model[1].Name); + + Assert.Equal(4, modelState.Count); + Assert.Equal(2, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "[0].Id").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "[1].Id").Value; + Assert.Equal("11", entry.Value.AttemptedValue); + Assert.Equal("11", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "[0].Name").Value; + Assert.Null(entry.Value); + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + + entry = Assert.Single(modelState, kvp => kvp.Key == "[1].Name").Value; + Assert.Null(entry.Value); + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + } + + [Fact(Skip = "Empty collection should be created by the collection model binder #1579")] + public async Task CollectionModelBinder_BindsListOfComplexType_WithRequiredProperty_NoData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(List) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); // This fails due to #1579 + Assert.False(modelBindingResult.IsModelSet); + Assert.Empty(Assert.IsType>(modelBindingResult.Model)); + + Assert.Equal(0, modelState.Count); + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + } } } \ 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 b8f99b9137..8a88a34675 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/MutableObjectModelBinderIntegrationTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Text; using System.Threading.Tasks; @@ -1565,6 +1566,464 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests Assert.Same(model.Customer.Address, entry.Value.RawValue); } + private class Order10 + { + [BindRequired] + public Person10 Customer { get; set; } + } + + private class Person10 + { + public string Name { get; set; } + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithRequiredComplexProperty_NoData_GetsErrors() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order10) + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Null(model.Customer); + + Assert.Equal(1, modelState.Count); // This fails due to #2446 + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "Customer").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["Customer"].Errors); + Assert.Equal("The Customer field is required.", error.ErrorMessage); + } + + private class Order11 + { + public Person11 Customer { get; set; } + } + + private class Person11 + { + public int Id { get; set; } + + [BindRequired] + public string Name { get; set; } + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithNestedRequiredProperty_WithPartialData_GetsErrors() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order11) + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.Customer.Id=123"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(model.Customer); + Assert.Equal(123, model.Customer.Id); + Assert.Null(model.Customer.Name); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Id").Value; + Assert.NotNull(entry.Value); + Assert.Equal("123", entry.Value.RawValue); + Assert.Equal("123", entry.Value.AttemptedValue); + + entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["parameter.Customer.Name"].Errors); + Assert.Equal("The Name field is required.", error.ErrorMessage); + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithNestedRequiredProperty_WithData_EmptyPrefix_GetsErrors() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order11) + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?Customer.Id=123"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(model.Customer); + Assert.Equal(123, model.Customer.Id); + Assert.Null(model.Customer.Name); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "Customer.Id").Value; + Assert.NotNull(entry.Value); + Assert.Equal("123", entry.Value.RawValue); + Assert.Equal("123", entry.Value.AttemptedValue); + + entry = Assert.Single(modelState, e => e.Key == "Customer.Name").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["Customer.Name"].Errors); + Assert.Equal("The Name field is required.", error.ErrorMessage); + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithNestedRequiredProperty_WithData_CustomPrefix_GetsErrors() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order11), + BindingInfo = new BindingInfo() + { + BinderModelName = "customParameter" + } + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?customParameter.Customer.Id=123"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.NotNull(model.Customer); + Assert.Equal(123, model.Customer.Id); + Assert.Null(model.Customer.Name); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "customParameter.Customer.Id").Value; + Assert.NotNull(entry.Value); + Assert.Equal("123", entry.Value.RawValue); + Assert.Equal("123", entry.Value.AttemptedValue); + + entry = Assert.Single(modelState, e => e.Key == "customParameter.Customer.Name").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["customParameter.Customer.Name"].Errors); + Assert.Equal("The Name field is required.", error.ErrorMessage); + } + + private class Order12 + { + [BindRequired] + public string ProductName { get; set; } + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithRequiredProperty_NoData_GetsErrors() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order12) + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Null(model.ProductName); + + Assert.Equal(1, modelState.Count); // This fails due to #2446 + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "ProductName").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["ProductName"].Errors); + Assert.Equal("The ProductName field is required.", error.ErrorMessage); + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithRequiredProperty_NoData_CustomPrefix_GetsErros() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order12), + BindingInfo = new BindingInfo() + { + BinderModelName = "customParameter" + } + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Null(model.ProductName); + + Assert.Equal(1, modelState.Count); + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "customParameter.ProductName").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["customParameter.ProductName"].Errors); + Assert.Equal("The ProductName field is required.", error.ErrorMessage); + } + + [Fact(Skip = "Extra model state entry due to #2446")] + public async Task MutableObjectModelBinder_WithRequiredProperty_WithData_EmptyPrefix_GetsBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order12), + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?ProductName=abc"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal("abc", model.ProductName); + + Assert.Equal(1, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "ProductName").Value; + Assert.NotNull(entry.Value); + Assert.Equal("abc", entry.Value.RawValue); + Assert.Equal("abc", entry.Value.AttemptedValue); + } + + private class Order13 + { + [BindRequired] + public List OrderIds { get; set; } + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithRequiredCollectionProperty_NoData_GetsErros() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order13) + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Null(model.OrderIds); + + Assert.Equal(1, modelState.Count); + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "OrderIds").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["OrderIds"].Errors); + Assert.Equal("The OrderIds field is required.", error.ErrorMessage); + } + + [Fact(Skip = "Error message is incorrect #2493.")] + public async Task MutableObjectModelBinder_WithRequiredCollectionProperty_NoData_CustomPrefix_GetsErros() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order13), + BindingInfo = new BindingInfo() + { + BinderModelName = "customParameter" + } + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Null(model.OrderIds); + + Assert.Equal(1, modelState.Count); + Assert.Equal(1, modelState.ErrorCount); + Assert.False(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "customParameter.OrderIds").Value; + Assert.Null(entry.Value); + var error = Assert.Single(modelState["customParameter.OrderIds"].Errors); + Assert.Equal("The OrderIds field is required.", error.ErrorMessage); + } + + [Fact(Skip = "Extra model state entry due to #2446")] + public async Task MutableObjectModelBinder_WithRequiredCollectionProperty_WithData_EmptyPrefix_GetsBound() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order13), + }; + + // No Data + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?OrderIds[0]=123"); + }); + + var modelState = new ModelStateDictionary(); + + // Act + var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); + + // Assert + Assert.NotNull(modelBindingResult); + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal(new[] { 123 }, model.OrderIds.ToArray()); + + Assert.Equal(1, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, e => e.Key == "OrderIds[0]").Value; + Assert.NotNull(entry.Value); + Assert.Equal("123", entry.Value.RawValue); + Assert.Equal("123", entry.Value.AttemptedValue); + } + private static void SetJsonBodyContent(HttpRequest request, string content) { var stream = new MemoryStream(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(content));