// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.ModelBinding; using Xunit; namespace Microsoft.AspNet.Mvc.IntegrationTests { // Integration tests targeting the behavior of the CollectionModelBinder with other model binders. // // Note that CollectionModelBinder handles both ICollection{T} and IList{T} public class CollectionModelBinderIntegrationTest { [Fact] public async Task CollectionModelBinder_BindsListOfSimpleType_WithPrefix_Success() { // 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]=10¶meter[1]=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(new List() { 10, 11 }, model); Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0]").Value; Assert.Equal("10", entry.Value.AttemptedValue); Assert.Equal("10", entry.Value.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1]").Value; Assert.Equal("11", entry.Value.AttemptedValue); Assert.Equal("11", entry.Value.RawValue); } [Fact] public async Task CollectionModelBinder_BindsListOfSimpleType_WithExplicitPrefix_Success() { // 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]=10&prefix[1]=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(new List() { 10, 11 }, model); Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0]").Value; Assert.Equal("10", entry.Value.AttemptedValue); Assert.Equal("10", entry.Value.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[1]").Value; Assert.Equal("11", entry.Value.AttemptedValue); Assert.Equal("11", entry.Value.RawValue); } [Fact] public async Task CollectionModelBinder_BindsCollectionOfSimpleType_EmptyPrefix_Success() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", ParameterType = typeof(ICollection) }; var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { request.QueryString = new QueryString("?[0]=10&[1]=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(new List { 10, 11 }, model); Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); var entry = Assert.Single(modelState, kvp => kvp.Key == "[0]").Value; Assert.Equal("10", entry.Value.AttemptedValue); Assert.Equal("10", entry.Value.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "[1]").Value; Assert.Equal("11", entry.Value.AttemptedValue); Assert.Equal("11", entry.Value.RawValue); } [Fact] public async Task CollectionModelBinder_BindsListOfSimpleType_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); Assert.True(modelBindingResult.IsModelSet); Assert.Empty(Assert.IsType>(modelBindingResult.Model)); Assert.Equal(0, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); } private class Person { public int Id { get; set; } } [Fact] public async Task CollectionModelBinder_BindsListOfComplexType_WithPrefix_Success() { // 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.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(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); } [Fact] public async Task CollectionModelBinder_BindsListOfComplexType_WithExplicitPrefix_Success() { // 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.Equal(11, model[1].Id); Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(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); } [Fact] public async Task CollectionModelBinder_BindsCollectionOfComplexType_EmptyPrefix_Success() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", ParameterType = typeof(List) }; 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.Equal(11, model[1].Id); Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(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); } [Fact] public async Task CollectionModelBinder_BindsListOfComplexType_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); Assert.True(modelBindingResult.IsModelSet); Assert.Empty(Assert.IsType>(modelBindingResult.Model)); Assert.Equal(0, modelState.Count); 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); 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] 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] 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); Assert.True(modelBindingResult.IsModelSet); Assert.Empty(Assert.IsType>(modelBindingResult.Model)); Assert.Equal(0, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); } private class Person4 { public IList Addresses { get; set; } } private class Address4 { public int Zip { get; set; } public string Street { get; set; } } [Fact] public async Task CollectionModelBinder_UsesCustomIndexes() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", ParameterType = typeof(Person4) }; var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { var formCollection = new FormCollection(new Dictionary() { { "Addresses.index", new [] { "Key1", "Key2" } }, { "Addresses[Key1].Street", new [] { "Street1" } }, { "Addresses[Key2].Street", new [] { "Street2" } }, }); request.Form = formCollection; request.ContentType = "application/x-www-form-urlencoded"; }); var modelState = new ModelStateDictionary(); // Act var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); // Assert Assert.NotNull(modelBindingResult); Assert.True(modelBindingResult.IsModelSet); Assert.IsType(modelBindingResult.Model); Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); var entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key1].Street").Value; Assert.Equal("Street1", entry.Value.AttemptedValue); Assert.Equal("Street1", entry.Value.RawValue); entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key2].Street").Value; Assert.Equal("Street2", entry.Value.AttemptedValue); Assert.Equal("Street2", entry.Value.RawValue); } private class Person5 { public IList Addresses { get; set; } } private class Address5 { public int Zip { get; set; } [StringLength(3)] public string Street { get; set; } } [Fact] public async Task CollectionModelBinder_UsesCustomIndexes_AddsErrorsWithCorrectKeys() { // Arrange var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", ParameterType = typeof(Person5) }; var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => { var formCollection = new FormCollection(new Dictionary() { { "Addresses.index", new [] { "Key1" } }, { "Addresses[Key1].Street", new [] { "Street1" } }, }); request.Form = formCollection; request.ContentType = "application/x-www-form-urlencoded"; }); var modelState = new ModelStateDictionary(); // Act var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext); // Assert Assert.NotNull(modelBindingResult); Assert.True(modelBindingResult.IsModelSet); Assert.IsType(modelBindingResult.Model); Assert.Equal(1, modelState.Count); Assert.Equal(1, modelState.ErrorCount); Assert.False(modelState.IsValid); var entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key1].Street").Value; var error = Assert.Single(entry.Errors); Assert.Equal("The field Street must be a string with a maximum length of 3.", error.ErrorMessage); } } }