diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs new file mode 100644 index 0000000000..7beea3dc05 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + // Integration tests targeting the behavior of the ArrayModelBinder with other model binders. + public class ArrayModelBinderIntegrationTest + { + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task ArrayModelBinder_BindsArrayOfSimpleType_WithPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(int[]) + }; + + 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 int[] { 10, 11 }, model); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + 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(Skip = "Extra ModelState key because of #2446")] + public async Task ArrayModelBinder_BindsArrayOfSimpleType_WithExplicitPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix", + }, + ParameterType = typeof(int[]) + }; + + 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 int[] { 10, 11 }, model); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + 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(Skip = "Extra ModelState key because of #2446")] + public async Task ArrayModelBinder_BindsArrayOfSimpleType_EmptyPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(int[]) + }; + + 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 int[] { 10, 11 }, model); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + 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(Skip = "Empty collection should be created by the collection model binder #1579")] + public async Task ArrayModelBinder_BindsArrayOfSimpleType_NoData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(int[]) + }; + + 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/CollectionModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs new file mode 100644 index 0000000000..780cec533e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +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(Skip = "Extra ModelState key because of #2446")] + 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); // This fails due to #2446 + 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(Skip = "Extra ModelState key because of #2446")] + 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); // This fails due to #2446 + 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(Skip = "Extra ModelState key because of #2446")] + 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); // This fails due to #2446 + 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(Skip = "Empty collection should be created by the collection model binder #1579")] + 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); // 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/DictionaryModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs new file mode 100644 index 0000000000..a10f3d8a3a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + // Integration tests targeting the behavior of the DictionaryModelBinder with other model binders. + public class DictionaryModelBinderIntegrationTest + { + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Dictionary) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value=10"); + }); + + 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 Dictionary() { { "key0", 10 } }, model); + + Assert.Equal(2, modelState.Count); // Fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0].Key").Value; + Assert.Equal("key0", entry.Value.AttemptedValue); + Assert.Equal("key0", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0].Value").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + } + + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithExplicitPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix", + }, + ParameterType = typeof(Dictionary) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?prefix[0].Key=key0&prefix[0].Value=10"); + }); + + 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 Dictionary() { { "key0", 10 }, }, model); + + Assert.Equal(2, modelState.Count); // Fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0].Key").Value; + Assert.Equal("key0", entry.Value.AttemptedValue); + Assert.Equal("key0", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0].Value").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + } + + [Fact(Skip = "Extra ModelState key because of #2446, IsValue == false because of #2470")] + public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Dictionary) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?[0].Key=key0&[0].Value=10"); + }); + + 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 Dictionary() { { "key0", 10 }, }, model); + + Assert.Equal(2, modelState.Count); // Fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); // Fails due to #2470 + + var entry = Assert.Single(modelState, kvp => kvp.Key == "[0].Key").Value; + Assert.Equal("key0", entry.Value.AttemptedValue); + Assert.Equal("key0", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "[0].Value").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + } + + [Fact(Skip = "Empty collection should be created by the collection model binder #1579")] + public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_NoData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Dictionary) + }; + + 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/GenericModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs new file mode 100644 index 0000000000..7cca3212d2 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + // Integration tests targeting the behavior of the GenericModelBinder and related classes + // with other model binders. + 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. + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_WithPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(List) + }; + + // Need to have a key here so that the GenericModelBinder will recurse to bind elements. + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.index=10"); + }); + + 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(1, model.Count); + Assert.NotNull(model[0]); + + Assert.Equal(0, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + } + + // 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. + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_EmptyPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(List) + }; + // Need to have a key here so that the GenericModelBinder will recurse to bind elements. + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?index=10"); + }); + + 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(1, model.Count); + Assert.NotNull(model[0]); + + Assert.Equal(0, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + } + + // 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. + [Fact(Skip = "Empty collection should be created by the collection model binder #1579")] + public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_NoData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(List) + }; + + // Without a key here so the GenericModelBinder will not recurse to bind elements. + 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); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Empty(model); + } + + [BindAddress] + private class Address + { + } + + private class BindAddressAttribute : Attribute, IBindingSourceMetadata + { + public static readonly BindingSource Source = new BindingSource( + "Address", + displayName: "Address", + isGreedy: true, + isFromRequest: true); + + public BindingSource BindingSource + { + get + { + return Source; + } + } + } + + private class AddressBinder : BindingSourceModelBinder + { + public AddressBinder() + : base(BindAddressAttribute.Source) + { + } + + protected override Task BindModelCoreAsync(ModelBindingContext bindingContext) + { + return Task.FromResult(new ModelBindingResult( + new Address(), + bindingContext.ModelName, + isModelSet: true)); + } + } + + // 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 binding source. + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task GenericModelBinder_BindsCollection_ElementTypeUsesGreedyModelBinder_WithPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Address[]) + }; + + // Need to have a key here so that the GenericModelBinder will recurse to bind elements. + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.index=0"); + }); + + 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(1, model.Length); + Assert.NotNull(model[0]); + + Assert.Equal(0, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs new file mode 100644 index 0000000000..4a5074fe9a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.IntegrationTests +{ + // Integration tests targeting the behavior of the KeyValuePairModelBinder with other model binders. + public class KeyValuePairModelBinderIntegrationTest + { + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task KeyValuePairModelBinder_BindsKeyValuePairOfSimpleType_WithPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?parameter.Key=key0¶meter.Value=10"); + }); + + 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 KeyValuePair("key0", 10), model); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Key").Value; + Assert.Equal("key0", entry.Value.AttemptedValue); + Assert.Equal("key0", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "parameter.Value").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + } + + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task KeyValuePairModelBinder_BindsKeyValuePairOfSimpleType_WithExplicitPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + BindingInfo = new BindingInfo() + { + BinderModelName = "prefix", + }, + ParameterType = typeof(KeyValuePair) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?prefix.Key=key0&prefix.Value=10"); + }); + + 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 KeyValuePair("key0", 10), model); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix.Key").Value; + Assert.Equal("key0", entry.Value.AttemptedValue); + Assert.Equal("key0", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "prefix.Value").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + } + + [Fact(Skip = "Extra ModelState key because of #2446")] + public async Task KeyValuePairModelBinder_BindsKeyValuePairOfSimpleType_EmptyPrefix_Success() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + + var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => + { + request.QueryString = new QueryString("?Key=key0&Value=10"); + }); + + 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 KeyValuePair("key0", 10), model); + + Assert.Equal(2, modelState.Count); // This fails due to #2446 + Assert.Equal(0, modelState.ErrorCount); + Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState, kvp => kvp.Key == "Key").Value; + Assert.Equal("key0", entry.Value.AttemptedValue); + Assert.Equal("key0", entry.Value.RawValue); + + entry = Assert.Single(modelState, kvp => kvp.Key == "Value").Value; + Assert.Equal("10", entry.Value.AttemptedValue); + Assert.Equal("10", entry.Value.RawValue); + } + + [Fact(Skip = "Empty collection should be created by the collection model binder #1579")] + public async Task KeyValuePairModelBinder_BindsKeyValuePairOfSimpleType_NoData() + { + // Arrange + var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(KeyValuePair) + }; + + 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.Equal(new int[0], 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/ModelBindingTestHelper.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs index 27eca799d7..b62f88f28f 100644 --- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -12,11 +12,30 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests { public static class ModelBindingTestHelper { - public static OperationBindingContext GetOperationBindingContext(Action updateRequest) + public static HttpContext GetHttpContext( + Action updateRequest = null, + Action updateOptions = null) { - var httpContext = ModelBindingTestHelper.GetHttpContext(updateRequest); - var actionBindingContext = - httpContext.RequestServices.GetRequiredService>().Value; + var httpContext = new DefaultHttpContext(); + + if (updateRequest != null) + { + updateRequest(httpContext.Request); + } + + InitializeServices(httpContext, updateOptions); + return httpContext; + } + + public static OperationBindingContext GetOperationBindingContext( + Action updateRequest = null, + Action updateOptions = null) + { + var httpContext = GetHttpContext(updateRequest, updateOptions); + + var services = httpContext.RequestServices; + var actionBindingContext = services.GetRequiredService>().Value; + return new OperationBindingContext() { BodyBindingState = BodyBindingState.NotBodyBased, @@ -40,13 +59,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests metadataProvider)); } - public static HttpContext GetHttpContext(Action updateRequest) + private static void InitializeServices(HttpContext httpContext, Action updateOptions = null) { - var options = (new TestMvcOptions()).Options; - var httpContext = new DefaultHttpContext(); - - updateRequest(httpContext.Request); - var serviceCollection = MvcServices.GetDefaultServices(); httpContext.RequestServices = serviceCollection.BuildServiceProvider(); @@ -56,10 +70,15 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests httpContext.RequestServices.GetRequiredService>(); actionContextAccessor.Value = actionContext; + var options = new TestMvcOptions().Options; + if (updateOptions != null) + { + updateOptions(options); + } + var actionBindingContextAccessor = httpContext.RequestServices.GetRequiredService>(); actionBindingContextAccessor.Value = GetActionBindingContext(options, actionContext); - return httpContext; } private static ActionBindingContext GetActionBindingContext(MvcOptions options, ActionContext actionContext) @@ -82,4 +101,4 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests }; } } -} \ No newline at end of file +}