Add support for model binding dictionaries from `prefix[name]=value` entries
- #1418 - add new fallback binding in `DictionaryModelBinder` - similar to MVC 5 approach but more explicit and with better key conversion support - fix bugs in `PrefixContainer` encountered while adding new tests of #1418 scenarios - did not handle entries like "[key]" or "prefix.key[index]" correctly - refactor part of `GetKeyFromEmptyPrefix()` into `IndexOfDelimiter()`; share with `GetKeyFromNonEmptyPrefix()` - extend `ReadableStringCollectionValueProviderTest` to cover bracketed key segments nits: - remove use of "foo", "bar", and "baz" in affected test classes - `""` -> `string.Empty` - `vpResult` -> `result`
This commit is contained in:
parent
6033679193
commit
79a2982441
|
|
@ -2,7 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
|
|
@ -13,15 +16,88 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// <typeparam name="TValue">Type of values in the dictionary.</typeparam>
|
/// <typeparam name="TValue">Type of values in the dictionary.</typeparam>
|
||||||
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
|
public class DictionaryModelBinder<TKey, TValue> : CollectionModelBinder<KeyValuePair<TKey, TValue>>
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override async Task<ModelBindingResult> 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<TKey, TValue>)result.Model;
|
||||||
|
if (model.Count != 0)
|
||||||
|
{
|
||||||
|
// ICollection<KeyValuePair<TKey, TValue>> 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<TValue>(valueResult?.Model);
|
||||||
|
if (valueResult != null && valueResult.IsModelSet)
|
||||||
|
{
|
||||||
|
validationNode.ChildNodes.Add(valueResult.ValidationNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override object GetModel(IEnumerable<KeyValuePair<TKey, TValue>> newCollection)
|
protected override object GetModel(IEnumerable<KeyValuePair<TKey, TValue>> newCollection)
|
||||||
{
|
{
|
||||||
return newCollection?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
return newCollection?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
protected override object CreateEmptyCollection()
|
protected override object CreateEmptyCollection()
|
||||||
{
|
{
|
||||||
return new Dictionary<TKey, TValue>();
|
return new Dictionary<TKey, TValue>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<TKey>(keyObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,31 +81,34 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private static void GetKeyFromEmptyPrefix(string entry, IDictionary<string, string> results)
|
private static void GetKeyFromEmptyPrefix(string entry, IDictionary<string, string> results)
|
||||||
{
|
{
|
||||||
var dotPosition = entry.IndexOf('.');
|
string key;
|
||||||
var bracketPosition = entry.IndexOf('[');
|
string fullName;
|
||||||
var delimiterPosition = -1;
|
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
|
else
|
||||||
{
|
{
|
||||||
if (bracketPosition == -1)
|
// Handle an entry such as "key", "key.property" and "key[index]".
|
||||||
{
|
key = delimiterPosition == -1 ? entry : entry.Substring(0, delimiterPosition);
|
||||||
delimiterPosition = dotPosition;
|
fullName = key;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
delimiterPosition = Math.Min(dotPosition, bracketPosition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = delimiterPosition == -1 ? entry : entry.Substring(0, delimiterPosition);
|
if (!results.ContainsKey(key))
|
||||||
results[key] = key;
|
{
|
||||||
|
results.Add(key, fullName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetKeyFromNonEmptyPrefix(string prefix, string entry, IDictionary<string, string> results)
|
private static void GetKeyFromNonEmptyPrefix(string prefix, string entry, IDictionary<string, string> results)
|
||||||
|
|
@ -117,17 +120,23 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
switch (entry[prefix.Length])
|
switch (entry[prefix.Length])
|
||||||
{
|
{
|
||||||
case '.':
|
case '.':
|
||||||
var dotPosition = entry.IndexOf('.', keyPosition);
|
// Handle an entry such as "prefix.key", "prefix.key.property" and "prefix.key[index]".
|
||||||
if (dotPosition == -1)
|
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;
|
break;
|
||||||
|
|
||||||
case '[':
|
case '[':
|
||||||
|
// Handle an entry such as "prefix[key]".
|
||||||
var bracketPosition = entry.IndexOf(']', keyPosition);
|
var bracketPosition = entry.IndexOf(']', keyPosition);
|
||||||
if (bracketPosition == -1)
|
if (bracketPosition == -1)
|
||||||
{
|
{
|
||||||
|
|
@ -140,6 +149,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
// Ignore an entry such as "prefixA".
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert an ICollection to an array, removing null values. Fast path for case where
|
/// Convert an ICollection to an array, removing null values. Fast path for case where
|
||||||
/// there are no null values.
|
/// there are no null values.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
#if DNX451
|
#if DNX451
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -64,6 +67,200 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
Assert.Equal("eighty-four", dictionary[84]);
|
Assert.Equal("eighty-four", dictionary[84]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modelName, keyFormat, dictionary
|
||||||
|
public static TheoryData<string, string, IDictionary<string, string>> StringToStringData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var dictionaryWithOne = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
{ "one", "one" },
|
||||||
|
};
|
||||||
|
var dictionaryWithThree = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
{ "one", "one" },
|
||||||
|
{ "two", "two" },
|
||||||
|
{ "three", "three" },
|
||||||
|
};
|
||||||
|
|
||||||
|
return new TheoryData<string, string, IDictionary<string, string>>
|
||||||
|
{
|
||||||
|
{ 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<string, string> dictionary)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var binder = new DictionaryModelBinder<string, string>();
|
||||||
|
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<IDictionary<string, string>>(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<string, string>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
{ "one", "one" },
|
||||||
|
{ "two", "two" },
|
||||||
|
{ "three", "three" },
|
||||||
|
};
|
||||||
|
|
||||||
|
var binder = new DictionaryModelBinder<string, string>();
|
||||||
|
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<IDictionary<string, string>>(result.Model);
|
||||||
|
Assert.Empty(resultDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<IDictionary<long, int>> LongToIntData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var dictionaryWithOne = new Dictionary<long, int>
|
||||||
|
{
|
||||||
|
{ 0L, 0 },
|
||||||
|
};
|
||||||
|
var dictionaryWithThree = new Dictionary<long, int>
|
||||||
|
{
|
||||||
|
{ -1L, -1 },
|
||||||
|
{ long.MaxValue, int.MaxValue },
|
||||||
|
{ long.MinValue, int.MinValue },
|
||||||
|
};
|
||||||
|
|
||||||
|
return new TheoryData<IDictionary<long, int>> { dictionaryWithOne, dictionaryWithThree };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LongToIntData))]
|
||||||
|
public async Task BindModel_FallsBackToBindingValues_WithValueTypes(IDictionary<long, int> dictionary)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var stringDictionary = dictionary.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.ToString());
|
||||||
|
var binder = new DictionaryModelBinder<long, int>();
|
||||||
|
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<IDictionary<long, int>>(result.Model);
|
||||||
|
Assert.Equal(dictionary, resultDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BindModel_FallsBackToBindingValues_WithComplexValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var dictionary = new Dictionary<int, ModelWithProperties>
|
||||||
|
{
|
||||||
|
{ 23, new ModelWithProperties { Id = 43, Name = "Wilma" } },
|
||||||
|
{ 27, new ModelWithProperties { Id = 98, Name = "Fred" } },
|
||||||
|
};
|
||||||
|
var stringDictionary = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "prefix[23].Id", "43" },
|
||||||
|
{ "prefix[23].Name", "Wilma" },
|
||||||
|
{ "prefix[27].Id", "98" },
|
||||||
|
{ "prefix[27].Name", "Fred" },
|
||||||
|
};
|
||||||
|
var binder = new DictionaryModelBinder<int, ModelWithProperties>();
|
||||||
|
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<IDictionary<int, ModelWithProperties>>(result.Model);
|
||||||
|
Assert.Equal(dictionary, resultDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
|
public async Task DictionaryModelBinder_DoesNotCreateCollection_IfIsTopLevelObjectAndIsFirstChanceBinding()
|
||||||
{
|
{
|
||||||
|
|
@ -161,6 +358,46 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
return modelBindingContext;
|
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<string, string> dictionary)
|
||||||
|
{
|
||||||
|
// Convert to an IDictionary<string, string[]> 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<string, string> dictionary)
|
||||||
|
{
|
||||||
|
// Convert to an IDictionary<string, object> 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)
|
private static ModelBindingContext GetModelBindingContext(bool isReadOnly)
|
||||||
{
|
{
|
||||||
var metadataProvider = new TestModelMetadataProvider();
|
var metadataProvider = new TestModelMetadataProvider();
|
||||||
|
|
@ -208,6 +445,32 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
public Dictionary<string, string> DictionaryProperty { get; set; }
|
public Dictionary<string, string> 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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
private static readonly IReadableStringCollection _backingStore = new ReadableStringCollection(
|
private static readonly IReadableStringCollection _backingStore = new ReadableStringCollection(
|
||||||
new Dictionary<string, string[]>
|
new Dictionary<string, string[]>
|
||||||
{
|
{
|
||||||
{ "foo", new[] { "fooValue1", "fooValue2"} },
|
{ "some", new[] { "someValue1", "someValue2" } },
|
||||||
{ "bar.baz", new[] {"someOtherValue" } },
|
|
||||||
{ "null_value", null },
|
{ "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]
|
[Fact]
|
||||||
|
|
@ -30,7 +39,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, backingStore, null);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, backingStore, null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await valueProvider.ContainsPrefixAsync("");
|
var result = await valueProvider.ContainsPrefixAsync(string.Empty);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
@ -43,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await valueProvider.ContainsPrefixAsync("");
|
var result = await valueProvider.ContainsPrefixAsync(string.Empty);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
@ -56,9 +65,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
Assert.True(await valueProvider.ContainsPrefixAsync("foo"));
|
Assert.True(await valueProvider.ContainsPrefixAsync("some"));
|
||||||
Assert.True(await valueProvider.ContainsPrefixAsync("bar"));
|
Assert.True(await valueProvider.ContainsPrefixAsync("prefix"));
|
||||||
Assert.True(await valueProvider.ContainsPrefixAsync("bar.baz"));
|
Assert.True(await valueProvider.ContainsPrefixAsync("prefix.name"));
|
||||||
|
Assert.True(await valueProvider.ContainsPrefixAsync("[index]"));
|
||||||
|
Assert.True(await valueProvider.ContainsPrefixAsync("prefix[index1]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -80,15 +91,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new Dictionary<string, string>
|
var expected = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "bar", "bar" },
|
{ "index", "[index]" },
|
||||||
{ "foo", "foo" },
|
|
||||||
{ "null_value", "null_value" },
|
{ "null_value", "null_value" },
|
||||||
{ "prefix", "prefix" }
|
{ "prefix", "prefix" },
|
||||||
|
{ "some", "some" },
|
||||||
};
|
};
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture: null);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await valueProvider.GetKeysFromPrefixAsync("");
|
var result = await valueProvider.GetKeysFromPrefixAsync(string.Empty);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expected, result.OrderBy(kvp => kvp.Key));
|
Assert.Equal(expected, result.OrderBy(kvp => kvp.Key));
|
||||||
|
|
@ -111,15 +122,40 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
public async Task GetKeysFromPrefixAsync_KnownPrefix_ReturnsMatchingItems()
|
public async Task GetKeysFromPrefixAsync_KnownPrefix_ReturnsMatchingItems()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var expected = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "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);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await valueProvider.GetKeysFromPrefixAsync("bar");
|
var result = await valueProvider.GetKeysFromPrefixAsync("prefix");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var kvp = Assert.Single(result);
|
Assert.Equal(expected, result);
|
||||||
Assert.Equal("baz", kvp.Key);
|
}
|
||||||
Assert.Equal("bar.baz", kvp.Value);
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetKeysFromPrefixAsync_IndexPrefix_ReturnsMatchingItems()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "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]
|
[Fact]
|
||||||
|
|
@ -130,13 +166,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var vpResult = await valueProvider.GetValueAsync("bar.baz");
|
var result = await valueProvider.GetValueAsync("prefix.name");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(vpResult);
|
Assert.NotNull(result);
|
||||||
Assert.Equal("someOtherValue", vpResult.RawValue);
|
Assert.Equal("someOtherValue", result.RawValue);
|
||||||
Assert.Equal("someOtherValue", vpResult.AttemptedValue);
|
Assert.Equal("someOtherValue", result.AttemptedValue);
|
||||||
Assert.Equal(culture, vpResult.Culture);
|
Assert.Equal(culture, result.Culture);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -147,13 +183,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, culture);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var vpResult = await valueProvider.GetValueAsync("foo");
|
var result = await valueProvider.GetValueAsync("some");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(vpResult);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(new[] { "fooValue1", "fooValue2" }, (IList<string>)vpResult.RawValue);
|
Assert.Equal(new[] { "someValue1", "someValue2" }, (IList<string>)result.RawValue);
|
||||||
Assert.Equal("fooValue1,fooValue2", vpResult.AttemptedValue);
|
Assert.Equal("someValue1,someValue2", result.AttemptedValue);
|
||||||
Assert.Equal(culture, vpResult.Culture);
|
Assert.Equal(culture, result.Culture);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -185,11 +221,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, backingStore, culture);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, backingStore, culture);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var vpResult = await valueProvider.GetValueAsync("key");
|
var result = await valueProvider.GetValueAsync("key");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(new[] { null, null, "value" }, vpResult.RawValue as IEnumerable<string>);
|
Assert.Equal(new[] { null, null, "value" }, result.RawValue as IEnumerable<string>);
|
||||||
Assert.Equal(",,value", vpResult.AttemptedValue);
|
Assert.Equal(",,value", result.AttemptedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -199,10 +235,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
var valueProvider = new ReadableStringCollectionValueProvider(BindingSource.Query, _backingStore, null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var vpResult = await valueProvider.GetValueAsync("bar");
|
var result = await valueProvider.GetValueAsync("prefix");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(vpResult);
|
Assert.Null(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -101,13 +101,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
|
||||||
var client = server.CreateClient();
|
var client = server.CreateClient();
|
||||||
|
var expected = "6 feet";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPersonParentHeightAttribute");
|
var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPersonParentHeightAttribute");
|
||||||
|
var keyValuePairs = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(expression, expected),
|
||||||
|
};
|
||||||
|
var result = await GetPerson(client, keyValuePairs);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Parent.Attributes[height]", expression);
|
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
|
// Uses the expression p => p.Dependents[0].Dependents[0].Name
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
@ -14,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
public class DictionaryModelBinderIntegrationTest
|
public class DictionaryModelBinderIntegrationTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithPrefix_Success()
|
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithPrefixAndKVP_Success()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -55,7 +54,48 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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<string, int>)
|
||||||
|
};
|
||||||
|
|
||||||
|
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<Dictionary<string, int>>(modelBindingResult.Model);
|
||||||
|
Assert.Equal(new Dictionary<string, int>() { { "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
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -71,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
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();
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
@ -86,21 +126,15 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
||||||
Assert.Equal(new Dictionary<string, int>() { { "key0", 10 }, }, model);
|
Assert.Equal(new Dictionary<string, int>() { { "key0", 10 }, }, model);
|
||||||
|
|
||||||
Assert.Equal(2, modelState.Count);
|
Assert.NotEmpty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
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]
|
[Theory]
|
||||||
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success()
|
[InlineData("?[key0]=10")]
|
||||||
|
[InlineData("?[0].Key=key0&[0].Value=10")]
|
||||||
|
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success(string queryString)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -112,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||||
{
|
{
|
||||||
request.QueryString = new QueryString("?[0].Key=key0&[0].Value=10");
|
request.QueryString = new QueryString(queryString);
|
||||||
});
|
});
|
||||||
|
|
||||||
var modelState = new ModelStateDictionary();
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
@ -127,17 +161,9 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
||||||
Assert.Equal(new Dictionary<string, int>() { { "key0", 10 }, }, model);
|
Assert.Equal(new Dictionary<string, int>() { { "key0", 10 }, }, model);
|
||||||
|
|
||||||
Assert.Equal(2, modelState.Count);
|
Assert.NotEmpty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
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]
|
[Fact]
|
||||||
|
|
@ -164,9 +190,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(modelBindingResult);
|
Assert.NotNull(modelBindingResult);
|
||||||
Assert.True(modelBindingResult.IsModelSet);
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
Assert.Empty(Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model));
|
|
||||||
|
|
||||||
Assert.Equal(0, modelState.Count);
|
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
||||||
|
Assert.Empty(model);
|
||||||
|
|
||||||
|
Assert.Empty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
@ -174,10 +202,29 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
private class Person
|
private class Person
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
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]
|
[Theory]
|
||||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithPrefix_Success()
|
[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
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -189,7 +236,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
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();
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
@ -202,25 +249,18 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
Assert.True(modelBindingResult.IsModelSet);
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
||||||
Assert.Equal(1, model.Count);
|
Assert.Equal(new Dictionary<string, Person> { { "key0", new Person { Id = 10 } }, }, model);
|
||||||
Assert.Equal("key0", model.Keys.First());
|
|
||||||
Assert.Equal(model.Values, model.Values);
|
|
||||||
|
|
||||||
Assert.Equal(2, modelState.Count);
|
Assert.NotEmpty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
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]
|
[Theory]
|
||||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithExplicitPrefix_Success()
|
[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
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -236,7 +276,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
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();
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
@ -249,25 +289,17 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
Assert.True(modelBindingResult.IsModelSet);
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
||||||
Assert.Equal(1, model.Count);
|
Assert.Equal(new Dictionary<string, Person> { { "key0", new Person { Id = 10 } }, }, model);
|
||||||
Assert.Equal("key0", model.Keys.First());
|
|
||||||
Assert.Equal(model.Values, model.Values);
|
|
||||||
|
|
||||||
Assert.Equal(2, modelState.Count);
|
Assert.NotEmpty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
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]
|
[Theory]
|
||||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_EmptyPrefix_Success()
|
[InlineData("?[key0].Id=10")]
|
||||||
|
[InlineData("?[0].Key=key0&[0].Value.Id=10")]
|
||||||
|
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_EmptyPrefix_Success(string queryString)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||||
|
|
@ -279,7 +311,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
|
|
||||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
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();
|
var modelState = new ModelStateDictionary();
|
||||||
|
|
@ -292,21 +324,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
Assert.True(modelBindingResult.IsModelSet);
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
|
|
||||||
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
||||||
Assert.Equal(1, model.Count);
|
Assert.Equal(new Dictionary<string, Person> { { "key0", new Person { Id = 10 } }, }, model);
|
||||||
Assert.Equal("key0", model.Keys.First());
|
|
||||||
Assert.Equal(model.Values, model.Values);
|
|
||||||
|
|
||||||
Assert.Equal(2, modelState.Count);
|
Assert.NotEmpty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
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]
|
[Fact]
|
||||||
|
|
@ -333,9 +355,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(modelBindingResult);
|
Assert.NotNull(modelBindingResult);
|
||||||
Assert.True(modelBindingResult.IsModelSet);
|
Assert.True(modelBindingResult.IsModelSet);
|
||||||
Assert.Empty(Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model));
|
|
||||||
|
|
||||||
Assert.Equal(0, modelState.Count);
|
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
||||||
|
Assert.Empty(model);
|
||||||
|
|
||||||
|
Assert.Empty(modelState);
|
||||||
Assert.Equal(0, modelState.ErrorCount);
|
Assert.Equal(0, modelState.ErrorCount);
|
||||||
Assert.True(modelState.IsValid);
|
Assert.True(modelState.IsValid);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue