diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs
index 24384b1ec2..26c3dab010 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/DictionaryModelBinder.cs
@@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
@@ -13,15 +16,88 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// Type of values in the dictionary.
public class DictionaryModelBinder : CollectionModelBinder>
{
+ ///
+ public override async Task BindModelAsync([NotNull] ModelBindingContext bindingContext)
+ {
+ var result = await base.BindModelAsync(bindingContext);
+ if (result == null || !result.IsModelSet)
+ {
+ // No match for the prefix at all.
+ return result;
+ }
+
+ Debug.Assert(result.Model != null);
+ var model = (Dictionary)result.Model;
+ if (model.Count != 0)
+ {
+ // ICollection> approach was successful.
+ return result;
+ }
+
+ var enumerableValueProvider = bindingContext.ValueProvider as IEnumerableValueProvider;
+ if (enumerableValueProvider == null)
+ {
+ // No IEnumerableValueProvider available for the fallback approach. For example the user may have
+ // replaced the ValueProvider with something other than a CompositeValueProvider.
+ return result;
+ }
+
+ // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first.
+ var keys = await enumerableValueProvider.GetKeysFromPrefixAsync(bindingContext.ModelName);
+ if (!keys.Any())
+ {
+ // No entries with the expected keys.
+ return result;
+ }
+
+ // Update the existing successful but empty ModelBindingResult.
+ var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
+ var valueMetadata = metadataProvider.GetMetadataForType(typeof(TValue));
+ var valueBindingContext = ModelBindingContext.GetChildModelBindingContext(
+ bindingContext,
+ bindingContext.ModelName,
+ valueMetadata);
+
+ var modelBinder = bindingContext.OperationBindingContext.ModelBinder;
+ var validationNode = result.ValidationNode;
+
+ foreach (var key in keys)
+ {
+ var dictionaryKey = ConvertFromString(key.Key);
+ valueBindingContext.ModelName = key.Value;
+
+ var valueResult = await modelBinder.BindModelAsync(valueBindingContext);
+
+ // Always add an entry to the dictionary but validate only if binding was successful.
+ model[dictionaryKey] = ModelBindingHelper.CastOrDefault(valueResult?.Model);
+ if (valueResult != null && valueResult.IsModelSet)
+ {
+ validationNode.ChildNodes.Add(valueResult.ValidationNode);
+ }
+ }
+
+ return result;
+ }
+
///
protected override object GetModel(IEnumerable> newCollection)
{
return newCollection?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
+ ///
protected override object CreateEmptyCollection()
{
return new Dictionary();
}
+
+ private static TKey ConvertFromString(string keyString)
+ {
+ // Use InvariantCulture to convert string since ExpressionHelper.GetExpressionText() used that culture.
+ var keyResult = new ValueProviderResult(keyString);
+ var keyObject = keyResult.ConvertTo(typeof(TKey));
+
+ return ModelBindingHelper.CastOrDefault(keyObject);
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/PrefixContainer.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/PrefixContainer.cs
index 0d193d271f..636148cf4e 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/PrefixContainer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/PrefixContainer.cs
@@ -81,31 +81,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static void GetKeyFromEmptyPrefix(string entry, IDictionary results)
{
- var dotPosition = entry.IndexOf('.');
- var bracketPosition = entry.IndexOf('[');
- var delimiterPosition = -1;
+ string key;
+ string fullName;
+ var delimiterPosition = IndexOfDelimiter(entry, 0);
- if (dotPosition == -1)
+ if (delimiterPosition == 0 && entry[0] == '[')
{
- if (bracketPosition != -1)
+ // Handle an entry such as "[key]".
+ var bracketPosition = entry.IndexOf(']', 1);
+ if (bracketPosition == -1)
{
- delimiterPosition = bracketPosition;
+ // Malformed for dictionary.
+ return;
}
+
+ key = entry.Substring(1, bracketPosition - 1);
+ fullName = entry.Substring(0, bracketPosition + 1);
}
else
{
- if (bracketPosition == -1)
- {
- delimiterPosition = dotPosition;
- }
- else
- {
- delimiterPosition = Math.Min(dotPosition, bracketPosition);
- }
+ // Handle an entry such as "key", "key.property" and "key[index]".
+ key = delimiterPosition == -1 ? entry : entry.Substring(0, delimiterPosition);
+ fullName = key;
}
- var key = delimiterPosition == -1 ? entry : entry.Substring(0, delimiterPosition);
- results[key] = key;
+ if (!results.ContainsKey(key))
+ {
+ results.Add(key, fullName);
+ }
}
private static void GetKeyFromNonEmptyPrefix(string prefix, string entry, IDictionary results)
@@ -117,17 +120,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
switch (entry[prefix.Length])
{
case '.':
- var dotPosition = entry.IndexOf('.', keyPosition);
- if (dotPosition == -1)
+ // Handle an entry such as "prefix.key", "prefix.key.property" and "prefix.key[index]".
+ var delimiterPosition = IndexOfDelimiter(entry, keyPosition);
+ if (delimiterPosition == -1)
{
- dotPosition = entry.Length;
+ // Neither '.' nor '[' found later in the name. Use rest of the string.
+ key = entry.Substring(keyPosition);
+ fullName = entry;
+ }
+ else
+ {
+ key = entry.Substring(keyPosition, delimiterPosition - keyPosition);
+ fullName = entry.Substring(0, delimiterPosition);
}
-
- key = entry.Substring(keyPosition, dotPosition - keyPosition);
- fullName = entry.Substring(0, dotPosition);
break;
case '[':
+ // Handle an entry such as "prefix[key]".
var bracketPosition = entry.IndexOf(']', keyPosition);
if (bracketPosition == -1)
{
@@ -140,6 +149,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
break;
default:
+ // Ignore an entry such as "prefixA".
return;
}
@@ -188,6 +198,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
+ private static int IndexOfDelimiter(string entry, int startIndex)
+ {
+ int delimiterPosition;
+ var bracketPosition = entry.IndexOf('[', startIndex);
+ var dotPosition = entry.IndexOf('.', startIndex);
+
+ if (dotPosition == -1)
+ {
+ delimiterPosition = bracketPosition;
+ }
+ else if (bracketPosition == -1)
+ {
+ delimiterPosition = dotPosition;
+ }
+ else
+ {
+ delimiterPosition = Math.Min(dotPosition, bracketPosition);
+ }
+
+ return delimiterPosition;
+ }
+
///
/// Convert an ICollection to an array, removing null values. Fast path for case where
/// there are no null values.
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs
index b20d8c46f8..f7d13df7ce 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/DictionaryModelBinderTest.cs
@@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if DNX451
+using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Moq;
@@ -64,6 +67,200 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal("eighty-four", dictionary[84]);
}
+ // modelName, keyFormat, dictionary
+ public static TheoryData> StringToStringData
+ {
+ get
+ {
+ var dictionaryWithOne = new Dictionary(StringComparer.Ordinal)
+ {
+ { "one", "one" },
+ };
+ var dictionaryWithThree = new Dictionary(StringComparer.Ordinal)
+ {
+ { "one", "one" },
+ { "two", "two" },
+ { "three", "three" },
+ };
+
+ return new TheoryData>
+ {
+ { string.Empty, "[{0}]", dictionaryWithOne },
+ { string.Empty, "[{0}]", dictionaryWithThree },
+ { "prefix", "prefix[{0}]", dictionaryWithOne },
+ { "prefix", "prefix[{0}]", dictionaryWithThree },
+ { "prefix.property", "prefix.property[{0}]", dictionaryWithOne },
+ { "prefix.property", "prefix.property[{0}]", dictionaryWithThree },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(StringToStringData))]
+ public async Task BindModel_FallsBackToBindingValues(
+ string modelName,
+ string keyFormat,
+ IDictionary dictionary)
+ {
+ // Arrange
+ var binder = new DictionaryModelBinder();
+ var context = CreateContext();
+ context.ModelName = modelName;
+ context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
+ context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider(keyFormat, dictionary);
+ context.ValueProvider = context.OperationBindingContext.ValueProvider;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithDictionaryProperty),
+ nameof(ModelWithDictionaryProperty.DictionaryProperty));
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.False(result.IsFatalError);
+ Assert.True(result.IsModelSet);
+ Assert.Equal(modelName, result.Key);
+ Assert.NotNull(result.ValidationNode);
+
+ var resultDictionary = Assert.IsAssignableFrom>(result.Model);
+ Assert.Equal(dictionary, resultDictionary);
+ }
+
+ // Similar to one BindModel_FallsBackToBindingValues case but without an IEnumerableValueProvider.
+ [Fact]
+ public async Task BindModel_DoesNotFallBack_WithoutEnumerableValueProvider()
+ {
+ // Arrange
+ var dictionary = new Dictionary(StringComparer.Ordinal)
+ {
+ { "one", "one" },
+ { "two", "two" },
+ { "three", "three" },
+ };
+
+ var binder = new DictionaryModelBinder();
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
+ context.OperationBindingContext.ValueProvider = CreateTestValueProvider("prefix[{0}]", dictionary);
+ context.ValueProvider = context.OperationBindingContext.ValueProvider;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithDictionaryProperty),
+ nameof(ModelWithDictionaryProperty.DictionaryProperty));
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.False(result.IsFatalError);
+ Assert.True(result.IsModelSet);
+ Assert.Equal("prefix", result.Key);
+ Assert.NotNull(result.ValidationNode);
+
+ var resultDictionary = Assert.IsAssignableFrom>(result.Model);
+ Assert.Empty(resultDictionary);
+ }
+
+ public static TheoryData> LongToIntData
+ {
+ get
+ {
+ var dictionaryWithOne = new Dictionary
+ {
+ { 0L, 0 },
+ };
+ var dictionaryWithThree = new Dictionary
+ {
+ { -1L, -1 },
+ { long.MaxValue, int.MaxValue },
+ { long.MinValue, int.MinValue },
+ };
+
+ return new TheoryData> { dictionaryWithOne, dictionaryWithThree };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(LongToIntData))]
+ public async Task BindModel_FallsBackToBindingValues_WithValueTypes(IDictionary dictionary)
+ {
+ // Arrange
+ var stringDictionary = dictionary.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.ToString());
+ var binder = new DictionaryModelBinder();
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
+ context.OperationBindingContext.ValueProvider =
+ CreateEnumerableValueProvider("prefix[{0}]", stringDictionary);
+ context.ValueProvider = context.OperationBindingContext.ValueProvider;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithDictionaryProperty),
+ nameof(ModelWithDictionaryProperty.DictionaryProperty));
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.False(result.IsFatalError);
+ Assert.True(result.IsModelSet);
+ Assert.Equal("prefix", result.Key);
+ Assert.NotNull(result.ValidationNode);
+
+ var resultDictionary = Assert.IsAssignableFrom>(result.Model);
+ Assert.Equal(dictionary, resultDictionary);
+ }
+
+ [Fact]
+ public async Task BindModel_FallsBackToBindingValues_WithComplexValues()
+ {
+ // Arrange
+ var dictionary = new Dictionary
+ {
+ { 23, new ModelWithProperties { Id = 43, Name = "Wilma" } },
+ { 27, new ModelWithProperties { Id = 98, Name = "Fred" } },
+ };
+ var stringDictionary = new Dictionary
+ {
+ { "prefix[23].Id", "43" },
+ { "prefix[23].Name", "Wilma" },
+ { "prefix[27].Id", "98" },
+ { "prefix[27].Name", "Fred" },
+ };
+ var binder = new DictionaryModelBinder();
+ var context = CreateContext();
+ context.ModelName = "prefix";
+ context.OperationBindingContext.ModelBinder = CreateCompositeBinder();
+ context.OperationBindingContext.ValueProvider = CreateEnumerableValueProvider("{0}", stringDictionary);
+ context.ValueProvider = context.OperationBindingContext.ValueProvider;
+
+ var metadataProvider = context.OperationBindingContext.MetadataProvider;
+ context.ModelMetadata = metadataProvider.GetMetadataForProperty(
+ typeof(ModelWithDictionaryProperty),
+ nameof(ModelWithDictionaryProperty.DictionaryProperty));
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.False(result.IsFatalError);
+ Assert.True(result.IsModelSet);
+ Assert.Equal("prefix", result.Key);
+ Assert.NotNull(result.ValidationNode);
+
+ var resultDictionary = Assert.IsAssignableFrom>(result.Model);
+ Assert.Equal(dictionary, resultDictionary);
+ }
+
[Fact]
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
{
@@ -161,6 +358,46 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
return modelBindingContext;
}
+ private static IModelBinder CreateCompositeBinder()
+ {
+ var binders = new IModelBinder[]
+ {
+ new TypeConverterModelBinder(),
+ new TypeMatchModelBinder(),
+ new MutableObjectModelBinder(),
+ new ComplexModelDtoModelBinder(),
+ };
+
+ return new CompositeModelBinder(binders);
+ }
+
+ private static IValueProvider CreateEnumerableValueProvider(
+ string keyFormat,
+ IDictionary dictionary)
+ {
+ // Convert to an IDictionary then wrap it up.
+ var backingStore = dictionary.ToDictionary(
+ kvp => string.Format(keyFormat, kvp.Key),
+ kvp => new[] { kvp.Value });
+ var stringCollection = new ReadableStringCollection(backingStore);
+
+ return new ReadableStringCollectionValueProvider(
+ BindingSource.Form,
+ stringCollection,
+ CultureInfo.InvariantCulture);
+ }
+
+ // Like CreateEnumerableValueProvider except returned instance does not implement IEnumerableValueProvider.
+ private static IValueProvider CreateTestValueProvider(string keyFormat, IDictionary dictionary)
+ {
+ // Convert to an IDictionary then wrap it up.
+ var backingStore = dictionary.ToDictionary(
+ kvp => string.Format(keyFormat, kvp.Key),
+ kvp => (object)kvp.Value);
+
+ return new TestValueProvider(BindingSource.Form, backingStore);
+ }
+
private static ModelBindingContext GetModelBindingContext(bool isReadOnly)
{
var metadataProvider = new TestModelMetadataProvider();
@@ -208,6 +445,32 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public Dictionary DictionaryProperty { get; set; }
}
+
+ private class ModelWithProperties
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as ModelWithProperties;
+ return other != null &&
+ Id == other.Id &&
+ string.Equals(Name, other.Name, StringComparison.Ordinal);
+ }
+
+ public override int GetHashCode()
+ {
+ int nameCode = Name == null ? 0 : Name.GetHashCode();
+ return nameCode ^ Id.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{{{ Id }, '{ Name }'}}";
+ }
+ }
}
}
#endif
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ReadableStringCollectionValueProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ReadableStringCollectionValueProviderTest.cs
index c3cef91d33..a8d51dc2ad 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ReadableStringCollectionValueProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/ReadableStringCollectionValueProviderTest.cs
@@ -16,10 +16,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
private static readonly IReadableStringCollection _backingStore = new ReadableStringCollection(
new Dictionary
{
- { "foo", new[] { "fooValue1", "fooValue2"} },
- { "bar.baz", new[] {"someOtherValue" } },
+ { "some", new[] { "someValue1", "someValue2" } },
{ "null_value", null },
- { "prefix.null_value", null }
+ { "prefix.name", new[] { "someOtherValue" } },
+ { "prefix.null_value", null },
+ { "prefix.property1.property", null },
+ { "prefix.property2[index]", null },
+ { "prefix[index1]", null },
+ { "prefix[index1].property1", null },
+ { "prefix[index1].property2", null },
+ { "prefix[index2].property", null },
+ { "[index]", null },
+ { "[index].property", null },
+ { "[index][anotherIndex]", null },
});
[Fact]
@@ -30,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, backingStore, null);
// Act
- var result = await valueProvider.ContainsPrefixAsync("");
+ var result = await valueProvider.ContainsPrefixAsync(string.Empty);
// Assert
Assert.False(result);
@@ -43,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
// Act
- var result = await valueProvider.ContainsPrefixAsync("");
+ var result = await valueProvider.ContainsPrefixAsync(string.Empty);
// Assert
Assert.True(result);
@@ -56,9 +65,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
// Act & Assert
- Assert.True(await valueProvider.ContainsPrefixAsync("foo"));
- Assert.True(await valueProvider.ContainsPrefixAsync("bar"));
- Assert.True(await valueProvider.ContainsPrefixAsync("bar.baz"));
+ Assert.True(await valueProvider.ContainsPrefixAsync("some"));
+ Assert.True(await valueProvider.ContainsPrefixAsync("prefix"));
+ Assert.True(await valueProvider.ContainsPrefixAsync("prefix.name"));
+ Assert.True(await valueProvider.ContainsPrefixAsync("[index]"));
+ Assert.True(await valueProvider.ContainsPrefixAsync("prefix[index1]"));
}
[Fact]
@@ -80,15 +91,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Arrange
var expected = new Dictionary
{
- { "bar", "bar" },
- { "foo", "foo" },
+ { "index", "[index]" },
{ "null_value", "null_value" },
- { "prefix", "prefix" }
+ { "prefix", "prefix" },
+ { "some", "some" },
};
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture: null);
// Act
- var result = await valueProvider.GetKeysFromPrefixAsync("");
+ var result = await valueProvider.GetKeysFromPrefixAsync(string.Empty);
// Assert
Assert.Equal(expected, result.OrderBy(kvp => kvp.Key));
@@ -111,15 +122,40 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public async Task GetKeysFromPrefixAsync_KnownPrefix_ReturnsMatchingItems()
{
// Arrange
+ var expected = new Dictionary
+ {
+ { "name", "prefix.name" },
+ { "null_value", "prefix.null_value" },
+ { "property1", "prefix.property1" },
+ { "property2", "prefix.property2" },
+ { "index1", "prefix[index1]" },
+ { "index2", "prefix[index2]" },
+ };
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
// Act
- var result = await valueProvider.GetKeysFromPrefixAsync("bar");
+ var result = await valueProvider.GetKeysFromPrefixAsync("prefix");
// Assert
- var kvp = Assert.Single(result);
- Assert.Equal("baz", kvp.Key);
- Assert.Equal("bar.baz", kvp.Value);
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public async Task GetKeysFromPrefixAsync_IndexPrefix_ReturnsMatchingItems()
+ {
+ // Arrange
+ var expected = new Dictionary
+ {
+ { "property", "[index].property" },
+ { "anotherIndex", "[index][anotherIndex]" }
+ };
+ var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
+
+ // Act
+ var result = await valueProvider.GetKeysFromPrefixAsync("[index]");
+
+ // Assert
+ Assert.Equal(expected, result);
}
[Fact]
@@ -130,13 +166,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture);
// Act
- var vpResult = await valueProvider.GetValueAsync("bar.baz");
+ var result = await valueProvider.GetValueAsync("prefix.name");
// Assert
- Assert.NotNull(vpResult);
- Assert.Equal("someOtherValue", vpResult.RawValue);
- Assert.Equal("someOtherValue", vpResult.AttemptedValue);
- Assert.Equal(culture, vpResult.Culture);
+ Assert.NotNull(result);
+ Assert.Equal("someOtherValue", result.RawValue);
+ Assert.Equal("someOtherValue", result.AttemptedValue);
+ Assert.Equal(culture, result.Culture);
}
[Fact]
@@ -147,13 +183,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture);
// Act
- var vpResult = await valueProvider.GetValueAsync("foo");
+ var result = await valueProvider.GetValueAsync("some");
// Assert
- Assert.NotNull(vpResult);
- Assert.Equal(new[] { "fooValue1", "fooValue2" }, (IList)vpResult.RawValue);
- Assert.Equal("fooValue1,fooValue2", vpResult.AttemptedValue);
- Assert.Equal(culture, vpResult.Culture);
+ Assert.NotNull(result);
+ Assert.Equal(new[] { "someValue1", "someValue2" }, (IList)result.RawValue);
+ Assert.Equal("someValue1,someValue2", result.AttemptedValue);
+ Assert.Equal(culture, result.Culture);
}
[Theory]
@@ -185,11 +221,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, backingStore, culture);
// Act
- var vpResult = await valueProvider.GetValueAsync("key");
+ var result = await valueProvider.GetValueAsync("key");
// Assert
- Assert.Equal(new[] { null, null, "value" }, vpResult.RawValue as IEnumerable);
- Assert.Equal(",,value", vpResult.AttemptedValue);
+ Assert.Equal(new[] { null, null, "value" }, result.RawValue as IEnumerable);
+ Assert.Equal(",,value", result.AttemptedValue);
}
[Fact]
@@ -199,10 +235,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
// Act
- var vpResult = await valueProvider.GetValueAsync("bar");
+ var result = await valueProvider.GetValueAsync("prefix");
// Assert
- Assert.Null(vpResult);
+ Assert.Null(result);
}
[Fact]
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs
index 5195cfa7d4..d834e043ae 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs
@@ -101,13 +101,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
+ var expected = "6 feet";
// Act
var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPersonParentHeightAttribute");
+ var keyValuePairs = new[]
+ {
+ new KeyValuePair(expression, expected),
+ };
+ var result = await GetPerson(client, keyValuePairs);
// Assert
Assert.Equal("Parent.Attributes[height]", expression);
- // TODO: https://github.com/aspnet/Mvc/issues/1418 Requires resolution in model binding
+ Assert.Equal(expected, result.Parent.Attributes["height"]);
}
// Uses the expression p => p.Dependents[0].Dependents[0].Name
diff --git a/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs b/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs
index bec4e95844..e60eb1aacd 100644
--- a/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
@@ -14,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public class DictionaryModelBinderIntegrationTest
{
[Fact]
- public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithPrefix_Success()
+ public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithPrefixAndKVP_Success()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
@@ -55,7 +54,48 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
}
[Fact]
- public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithExplicitPrefix_Success()
+ public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithPrefixAndItem_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[key0]=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(0, modelState.ErrorCount);
+ Assert.True(modelState.IsValid);
+
+ var kvp = Assert.Single(modelState);
+ Assert.Equal("parameter[key0]", kvp.Key);
+ var entry = kvp.Value;
+ Assert.Equal("10", entry.Value.AttemptedValue);
+ Assert.Equal("10", entry.Value.RawValue);
+ }
+
+ [Theory]
+ [InlineData("?prefix[key0]=10")]
+ [InlineData("?prefix[0].Key=key0&prefix[0].Value=10")]
+ public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithExplicitPrefix_Success(
+ string queryString)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
@@ -71,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
- request.QueryString = new QueryString("?prefix[0].Key=key0&prefix[0].Value=10");
+ request.QueryString = new QueryString(queryString);
});
var modelState = new ModelStateDictionary();
@@ -86,21 +126,15 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var model = Assert.IsType>(modelBindingResult.Model);
Assert.Equal(new Dictionary() { { "key0", 10 }, }, model);
- Assert.Equal(2, modelState.Count);
+ Assert.NotEmpty(modelState);
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]
- public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success()
+ [Theory]
+ [InlineData("?[key0]=10")]
+ [InlineData("?[0].Key=key0&[0].Value=10")]
+ public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success(string queryString)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
@@ -112,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
- request.QueryString = new QueryString("?[0].Key=key0&[0].Value=10");
+ request.QueryString = new QueryString(queryString);
});
var modelState = new ModelStateDictionary();
@@ -127,17 +161,9 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var model = Assert.IsType>(modelBindingResult.Model);
Assert.Equal(new Dictionary() { { "key0", 10 }, }, model);
- Assert.Equal(2, modelState.Count);
+ Assert.NotEmpty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
-
- 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]
@@ -164,9 +190,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Assert
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
- Assert.Empty(Assert.IsType>(modelBindingResult.Model));
- Assert.Equal(0, modelState.Count);
+ var model = Assert.IsType>(modelBindingResult.Model);
+ Assert.Empty(model);
+
+ Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
@@ -174,10 +202,29 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
private class Person
{
public int Id { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as Person;
+
+ return other != null && Id == other.Id;
+ }
+
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return $"{{ { Id } }}";
+ }
}
- [Fact]
- public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithPrefix_Success()
+ [Theory]
+ [InlineData("?parameter[key0].Id=10")]
+ [InlineData("?parameter[0].Key=key0¶meter[0].Value.Id=10")]
+ public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithPrefix_Success(string queryString)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
@@ -189,7 +236,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
- request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value.Id=10");
+ request.QueryString = new QueryString(queryString);
});
var modelState = new ModelStateDictionary();
@@ -202,25 +249,18 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>(modelBindingResult.Model);
- Assert.Equal(1, model.Count);
- Assert.Equal("key0", model.Keys.First());
- Assert.Equal(model.Values, model.Values);
+ Assert.Equal(new Dictionary { { "key0", new Person { Id = 10 } }, }, model);
- Assert.Equal(2, modelState.Count);
+ Assert.NotEmpty(modelState);
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.Id").Value;
- Assert.Equal("10", entry.Value.AttemptedValue);
- Assert.Equal("10", entry.Value.RawValue);
}
- [Fact]
- public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithExplicitPrefix_Success()
+ [Theory]
+ [InlineData("?prefix[key0].Id=10")]
+ [InlineData("?prefix[0].Key=key0&prefix[0].Value.Id=10")]
+ public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithExplicitPrefix_Success(
+ string queryString)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
@@ -236,7 +276,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
- request.QueryString = new QueryString("?prefix[0].Key=key0&prefix[0].Value.Id=10");
+ request.QueryString = new QueryString(queryString);
});
var modelState = new ModelStateDictionary();
@@ -249,25 +289,17 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>(modelBindingResult.Model);
- Assert.Equal(1, model.Count);
- Assert.Equal("key0", model.Keys.First());
- Assert.Equal(model.Values, model.Values);
+ Assert.Equal(new Dictionary { { "key0", new Person { Id = 10 } }, }, model);
- Assert.Equal(2, modelState.Count);
+ Assert.NotEmpty(modelState);
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.Id").Value;
- Assert.Equal("10", entry.Value.AttemptedValue);
- Assert.Equal("10", entry.Value.RawValue);
}
- [Fact]
- public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_EmptyPrefix_Success()
+ [Theory]
+ [InlineData("?[key0].Id=10")]
+ [InlineData("?[0].Key=key0&[0].Value.Id=10")]
+ public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_EmptyPrefix_Success(string queryString)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
@@ -279,7 +311,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
- request.QueryString = new QueryString("?[0].Key=key0&[0].Value.Id=10");
+ request.QueryString = new QueryString(queryString);
});
var modelState = new ModelStateDictionary();
@@ -292,21 +324,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType>(modelBindingResult.Model);
- Assert.Equal(1, model.Count);
- Assert.Equal("key0", model.Keys.First());
- Assert.Equal(model.Values, model.Values);
+ Assert.Equal(new Dictionary { { "key0", new Person { Id = 10 } }, }, model);
- Assert.Equal(2, modelState.Count);
+ Assert.NotEmpty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
-
- 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.Id").Value;
- Assert.Equal("10", entry.Value.AttemptedValue);
- Assert.Equal("10", entry.Value.RawValue);
}
[Fact]
@@ -333,9 +355,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Assert
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
- Assert.Empty(Assert.IsType>(modelBindingResult.Model));
- Assert.Equal(0, modelState.Count);
+ var model = Assert.IsType>(modelBindingResult.Model);
+ Assert.Empty(model);
+
+ Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}