diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ArrayModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ArrayModelBinder.cs
index 2d68d0e5a2..f7df7ae430 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ArrayModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/ArrayModelBinder.cs
@@ -25,6 +25,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return base.BindModelAsync(bindingContext);
}
+ protected override object CreateEmptyCollection()
+ {
+ return new TElement[0];
+ }
+
///
protected override object GetModel(IEnumerable newCollection)
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/CollectionModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/CollectionModelBinder.cs
index 040f389dd4..fe4ffca3ac 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/CollectionModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/CollectionModelBinder.cs
@@ -23,8 +23,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
ModelBindingHelper.ValidateBindingContext(bindingContext);
+ object model;
+
if (!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
{
+ // If this is the fallback case, and we failed to find data as a top-level model, then generate a
+ // default 'empty' model and return it.
+ var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
+ var hasExplicitAlias = bindingContext.BinderModelName != null;
+
+ if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
+ {
+ model = CreateEmptyCollection();
+
+ var validationNode = new ModelValidationNode(
+ bindingContext.ModelName,
+ bindingContext.ModelMetadata,
+ model);
+
+ return new ModelBindingResult(
+ model,
+ bindingContext.ModelName,
+ isModelSet: true,
+ validationNode: validationNode);
+ }
+
return null;
}
@@ -46,7 +69,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
boundCollection = result.Model;
}
- var model = bindingContext.Model;
+ model = bindingContext.Model;
if (model == null)
{
model = GetModel(boundCollection);
@@ -64,6 +87,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
validationNode: result?.ValidationNode);
}
+ // Called when we're creating a default 'empty' model for a top level bind.
+ protected virtual object CreateEmptyCollection()
+ {
+ return new List();
+ }
+
// Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value
// is [ "1", "2" ] and needs to be converted to an int[].
internal async Task BindSimpleCollection(
@@ -165,8 +194,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var didBind = false;
object boundValue = null;
- var modelType = bindingContext.ModelType;
-
var result =
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);
if (result != null && result.IsModelSet)
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs
index f8bafd1532..24384b1ec2 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs
@@ -18,5 +18,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
return newCollection?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
+
+ protected override object CreateEmptyCollection()
+ {
+ return new Dictionary();
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs
index 12e285e6b4..62ebdf047f 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/KeyValuePairModelBinder.cs
@@ -62,8 +62,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
else
{
- // Caller (GenericModelBinder) was able to resolve a binder type and will create a ModelBindingResult
- // that exits current ModelBinding loop.
+ // If this is the fallback case, and we failed to find data as a top-level model, then generate a
+ // default 'empty' model and return it.
+ var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
+ var hasExplicitAlias = bindingContext.BinderModelName != null;
+
+ if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
+ {
+ var model = new KeyValuePair();
+
+ var validationNode = new ModelValidationNode(
+ bindingContext.ModelName,
+ bindingContext.ModelMetadata,
+ model);
+
+ return new ModelBindingResult(
+ model,
+ bindingContext.ModelName,
+ isModelSet: true,
+ validationNode: validationNode);
+ }
+
return null;
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs
index 5161ec3893..86c7752e51 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ArrayModelBinderTest.cs
@@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if DNX451
+using System.Collections.Generic;
using System.Threading.Tasks;
+using Microsoft.AspNet.Http.Internal;
using Moq;
using Xunit;
@@ -35,14 +37,105 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
}
[Fact]
- public async Task BindModelAsync_ValueProviderDoesNotContainPrefix_ReturnsNull()
+ public async Task ArrayModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
{
// Arrange
- var bindingContext = GetBindingContext(new SimpleHttpValueProvider());
- var binder = new ArrayModelBinder();
+ var binder = new ArrayModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "param";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[]));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
// Act
- var result = await binder.BindModelAsync(bindingContext);
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task ArrayModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
+ {
+ // Arrange
+ var binder = new ArrayModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = string.Empty;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[]));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Empty(Assert.IsType(result.Model));
+ Assert.Equal(string.Empty, result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Same(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Fact]
+ public async Task ArrayModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
+ {
+ // Arrange
+ var binder = new ArrayModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.BinderModelName = "prefix";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[]));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Empty(Assert.IsType(result.Model));
+ Assert.Equal("prefix", result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Same(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("param")]
+ public async Task ArrayModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
+ {
+ // Arrange
+ var binder = new ArrayModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty");
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithArrayProperty),
+ nameof(ModelWithArrayProperty.ArrayProperty));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
// Assert
Assert.Null(result);
@@ -155,6 +248,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
};
return bindingContext;
}
+
+ private static ModelBindingContext CreateContext()
+ {
+ var modelBindingContext = new ModelBindingContext()
+ {
+ OperationBindingContext = new OperationBindingContext()
+ {
+ HttpContext = new DefaultHttpContext(),
+ MetadataProvider = new TestModelMetadataProvider(),
+ }
+ };
+
+ return modelBindingContext;
+ }
+
+ private class ModelWithArrayProperty
+ {
+ public string[] ArrayProperty { get; set; }
+ }
}
}
#endif
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/CollectionModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/CollectionModelBinderTest.cs
index c0f4af2331..dbde65ee76 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/CollectionModelBinderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/CollectionModelBinderTest.cs
@@ -7,6 +7,7 @@ using System.Globalization;
using System.Linq;
#endif
using System.Threading.Tasks;
+using Microsoft.AspNet.Http.Internal;
#if DNX451
using Moq;
#endif
@@ -216,6 +217,111 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Null(boundCollection);
}
+ [Fact]
+ public async Task CollectionModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
+ {
+ // Arrange
+ var binder = new CollectionModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "param";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task CollectionModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
+ {
+ // Arrange
+ var binder = new CollectionModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = string.Empty;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Empty(Assert.IsType>(result.Model));
+ Assert.Equal(string.Empty, result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Same(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Fact]
+ public async Task CollectionModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
+ {
+ // Arrange
+ var binder = new CollectionModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.BinderModelName = "prefix";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Empty(Assert.IsType>(result.Model));
+ Assert.Equal("prefix", result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Same(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("param")]
+ public async Task CollectionModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
+ {
+ // Arrange
+ var binder = new CollectionModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithListProperty),
+ nameof(ModelWithListProperty.ListProperty));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
#if DNX451
[Fact]
public async Task BindSimpleCollection_SubBindingSucceeds()
@@ -284,5 +390,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
return mockIntBinder.Object;
}
#endif
+
+ private static ModelBindingContext CreateContext()
+ {
+ var modelBindingContext = new ModelBindingContext()
+ {
+ OperationBindingContext = new OperationBindingContext()
+ {
+ HttpContext = new DefaultHttpContext(),
+ MetadataProvider = new TestModelMetadataProvider(),
+ }
+ };
+
+ return modelBindingContext;
+ }
+
+ private class ModelWithListProperty
+ {
+ public List ListProperty { get; set; }
+ }
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs
index 34b0318aea..4ad8d02f32 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs
@@ -4,6 +4,7 @@
#if DNX451
using System.Collections.Generic;
using System.Threading.Tasks;
+using Microsoft.AspNet.Http.Internal;
using Moq;
using Xunit;
@@ -63,6 +64,125 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal("eighty-four", dictionary[84]);
}
+ [Fact]
+ public async Task DictionaryModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
+ {
+ // Arrange
+ var binder = new DictionaryModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "param";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task DictionaryModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
+ {
+ // Arrange
+ var binder = new DictionaryModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = string.Empty;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Empty(Assert.IsType>(result.Model));
+ Assert.Equal(string.Empty, result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Same(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Fact]
+ public async Task DictionaryModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
+ {
+ // Arrange
+ var binder = new DictionaryModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.BinderModelName = "prefix";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Empty(Assert.IsType>(result.Model));
+ Assert.Equal("prefix", result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Same(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("param")]
+ public async Task DictionaryModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
+ {
+ // Arrange
+ var binder = new DictionaryModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty");
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithDictionaryProperty),
+ nameof(ModelWithDictionaryProperty.DictionaryProperty));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ private static ModelBindingContext CreateContext()
+ {
+ var modelBindingContext = new ModelBindingContext()
+ {
+ OperationBindingContext = new OperationBindingContext()
+ {
+ HttpContext = new DefaultHttpContext(),
+ MetadataProvider = new TestModelMetadataProvider(),
+ }
+ };
+
+ return modelBindingContext;
+ }
+
private static ModelBindingContext GetModelBindingContext(bool isReadOnly)
{
var metadataProvider = new TestModelMetadataProvider();
@@ -105,6 +225,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
});
return mockKvpBinder.Object;
}
+
+ private class ModelWithDictionaryProperty
+ {
+ public Dictionary DictionaryProperty { get; set; }
+ }
}
}
#endif
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs
index 1b9b463e3e..257eda9b21 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/KeyValuePairModelBinderTest.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Moq;
using Xunit;
@@ -158,6 +159,126 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Empty(bindingContext.ModelState);
}
+ [Fact]
+ public async Task KeyValuePairModelBinder_DoesNotCreateCollection_ForTopLevelModel_OnFirstPass()
+ {
+ // Arrange
+ var binder = new KeyValuePairModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "param";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(KeyValuePair));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public async Task KeyValuePairModelBinder_CreatesEmptyCollection_ForTopLevelModel_OnFallback()
+ {
+ // Arrange
+ var binder = new KeyValuePairModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = string.Empty;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(KeyValuePair));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Equal(default(KeyValuePair), Assert.IsType>(result.Model));
+ Assert.Equal(string.Empty, result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Equal(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Fact]
+ public async Task KeyValuePairModelBinder_CreatesEmptyCollection_ForTopLevelModel_WithExplicitPrefix()
+ {
+ // Arrange
+ var binder = new KeyValuePairModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.BinderModelName = "prefix";
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForType(typeof(KeyValuePair));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+
+ Assert.Equal(default(KeyValuePair), Assert.IsType>(result.Model));
+ Assert.Equal("prefix", result.Key);
+ Assert.True(result.IsModelSet);
+
+ Assert.Equal(result.ValidationNode.Model, result.Model);
+ Assert.Same(result.ValidationNode.Key, result.Key);
+ Assert.Same(result.ValidationNode.ModelMetadata, context.ModelMetadata);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("param")]
+ public async Task KeyValuePairModelBinder_DoesNotCreateCollection_ForNonTopLevelModel(string prefix)
+ {
+ // Arrange
+ var binder = new KeyValuePairModelBinder();
+
+ var context = CreateContext();
+ context.ModelName = ModelNames.CreatePropertyModelName(prefix, "KeyValuePairProperty");
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithKeyValuePairProperty),
+ nameof(ModelWithKeyValuePairProperty.KeyValuePairProperty));
+
+ context.ValueProvider = new TestValueProvider(new Dictionary());
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ private static ModelBindingContext CreateContext()
+ {
+ var modelBindingContext = new ModelBindingContext()
+ {
+ OperationBindingContext = new OperationBindingContext()
+ {
+ HttpContext = new DefaultHttpContext(),
+ MetadataProvider = new TestModelMetadataProvider(),
+ ModelBinder = new TypeMatchModelBinder(),
+ }
+ };
+
+ return modelBindingContext;
+ }
+
private static ModelBindingContext GetBindingContext(
IValueProvider valueProvider,
IModelBinder innerBinder = null,
@@ -210,6 +331,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
});
return mockStringBinder.Object;
}
+
+ private class ModelWithKeyValuePairProperty
+ {
+ public KeyValuePair KeyValuePairProperty { get; set; }
+ }
}
}
#endif
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs
index 94e59a69ff..0f810fffe0 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs
@@ -138,7 +138,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("11", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task ArrayModelBinder_BindsArrayOfSimpleType_NoData()
{
// Arrange
@@ -160,8 +160,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
@@ -304,7 +304,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("lang", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task ArrayModelBinder_BindsArrayOfComplexType_NoData()
{
// Arrange
@@ -326,8 +326,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs
index 4203930dea..0087f5dbc4 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs
@@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); // Should be skipped. bug#2447
}
- [Fact]
+ [Fact(Skip = "ByteArrayModelBinder should return a non-null result #2456")]
public async Task BindParameter_NoData_DoesNotGetBound()
{
// Arrange
@@ -102,11 +102,15 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Assert
// ModelBindingResult
- Assert.Null(modelBindingResult);
+ Assert.NotNull(modelBindingResult);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
+
+ Assert.Equal("CustomParameter", modelBindingResult.Key);
+ Assert.True(modelBindingResult.IsModelSet);
+ Assert.Equal(new byte[0], modelBindingResult.Model);
}
[Fact(Skip = "ModelState.Value not set due to #2445")]
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs
index 30a25e1dd0..5eca2f79f7 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs
@@ -145,7 +145,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("11", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task CollectionModelBinder_BindsListOfSimpleType_NoData()
{
// Arrange
@@ -167,8 +167,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType>(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
@@ -311,7 +311,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("11", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task CollectionModelBinder_BindsListOfComplexType_NoData()
{
// Arrange
@@ -333,8 +333,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType>(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
@@ -510,7 +510,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task CollectionModelBinder_BindsListOfComplexType_WithRequiredProperty_NoData()
{
// Arrange
@@ -532,8 +532,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType>(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs
index 792e0b7157..7b72180e14 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs
@@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("10", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_NoData()
{
// Arrange
@@ -162,8 +162,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType>(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
@@ -309,7 +309,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("10", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_NoData()
{
// Arrange
@@ -331,8 +331,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Empty(Assert.IsType>(modelBindingResult.Model));
Assert.Equal(0, modelState.Count);
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs
index 4a32af6ddc..b1a026d06e 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs
@@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
//
// 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")]
+ [Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_NoData()
{
// Arrange
@@ -122,8 +122,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>(modelBindingResult.Model);
Assert.Empty(model);
@@ -298,7 +298,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task GenericModelBinder_BindsArrayOfDictionary_NoData()
{
// Arrange
@@ -320,8 +320,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType[]>(modelBindingResult.Model);
Assert.NotNull(model);
@@ -424,7 +424,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_NoData()
{
// Arrange
@@ -446,8 +446,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // Fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>>(modelBindingResult.Model);
Assert.NotNull(model);
@@ -559,7 +559,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task GenericModelBinder_BindsDictionaryOfList_NoData()
{
// Arrange
@@ -581,8 +581,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // Fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>>(modelBindingResult.Model);
Assert.NotNull(model);
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs
index 230a1b34c8..58071797f7 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs
@@ -139,7 +139,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("10", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task KeyValuePairModelBinder_BindsKeyValuePairOfSimpleType_NoData()
{
// Arrange
@@ -161,10 +161,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
- Assert.Equal(new int[0], modelBindingResult.Model);
+ Assert.Equal(new KeyValuePair(), modelBindingResult.Model);
Assert.Equal(0, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
@@ -306,7 +306,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("10", entry.Value.RawValue);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task KeyValuePairModelBinder_BindsKeyValuePairOfComplexType_NoData()
{
// Arrange
@@ -328,8 +328,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
- Assert.NotNull(modelBindingResult); // This fails due to #1579
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.NotNull(modelBindingResult);
+ Assert.True(modelBindingResult.IsModelSet);
Assert.Equal(new KeyValuePair(), modelBindingResult.Model);
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs
index 6b832ca281..dfccd8d93b 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/ValidationIntegrationTests.cs
@@ -974,7 +974,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Null(error.Exception);
}
- [Fact(Skip = "Empty collection should be created by the collection model binder #1579")]
+ [Fact]
public async Task Validation_StringLengthAttribute_OnProperyOfCollectionElement_NoData()
{
// Arrange
@@ -997,14 +997,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Assert
Assert.NotNull(modelBindingResult);
- Assert.False(modelBindingResult.IsModelSet);
+ Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>(modelBindingResult.Model);
Assert.Empty(model);
Assert.Equal(0, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
- Assert.False(modelState.IsValid);
+ Assert.True(modelState.IsValid);
}
private class Order11