Expand model types `GenericModelBinder` can handle
- #2993 - use `ClosedGenericMatcher` to handle e.g. non-generic model types implementing requested interfaces - reduce `IsAssignableFrom()` use since created binders use explicit casts i.e. handle explicit implementations - also add more integration tests covering various collection key formats, some with validation errors - merge a few `[Fact]`s into `[Theory]`s
This commit is contained in:
parent
cfd9bfe13b
commit
829a5c9046
|
|
@ -4,8 +4,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
#if DNXCORE50
|
||||
using System.Reflection;
|
||||
#endif
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||
{
|
||||
|
|
@ -45,8 +48,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var modelType = context.ModelType;
|
||||
|
||||
return GetArrayBinder(modelType) ??
|
||||
GetCollectionBinder(modelType) ??
|
||||
GetDictionaryBinder(modelType) ??
|
||||
GetCollectionBinder(modelType) ??
|
||||
GetEnumerableBinder(context) ??
|
||||
GetKeyValuePairBinder(modelType);
|
||||
}
|
||||
|
|
@ -58,6 +61,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var elementType = modelType.GetElementType();
|
||||
return typeof(ArrayModelBinder<>).MakeGenericType(elementType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -99,9 +103,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
else
|
||||
{
|
||||
// A non-null instance must be updated in-place. For that the instance must also implement
|
||||
// ICollection<T>. For example an IEnumerable<T> property may have a List<T> default value.
|
||||
var closedCollectionType = typeof(ICollection<>).MakeGenericType(modelTypeArguments);
|
||||
if (!closedCollectionType.IsAssignableFrom(context.Model.GetType()))
|
||||
// ICollection<T>. For example an IEnumerable<T> property may have a List<T> default value. Do not use
|
||||
// IsAssignableFrom() because that does not handle explicit interface implementations and binders all
|
||||
// perform explicit casts.
|
||||
var closedGenericInterface =
|
||||
ClosedGenericMatcher.ExtractGenericInterface(context.Model.GetType(), typeof(ICollection<>));
|
||||
if (closedGenericInterface == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
@ -112,11 +119,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
private static Type GetKeyValuePairBinder(Type modelType)
|
||||
{
|
||||
var modelTypeInfo = modelType.GetTypeInfo();
|
||||
if (modelTypeInfo.IsGenericType &&
|
||||
modelTypeInfo.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
|
||||
Debug.Assert(modelType != null);
|
||||
|
||||
// Since KeyValuePair is a value type, ExtractGenericInterface() succeeds only on an exact match.
|
||||
var closedGenericType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(KeyValuePair<,>));
|
||||
if (closedGenericType != null)
|
||||
{
|
||||
return typeof(KeyValuePairModelBinder<,>).MakeGenericType(modelTypeInfo.GenericTypeArguments);
|
||||
return typeof(KeyValuePairModelBinder<,>).MakeGenericType(modelType.GenericTypeArguments);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -144,28 +153,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Debug.Assert(supportedInterfaceType != null);
|
||||
Debug.Assert(modelType != null);
|
||||
|
||||
var modelTypeInfo = modelType.GetTypeInfo();
|
||||
if (!modelTypeInfo.IsGenericType || modelTypeInfo.IsGenericTypeDefinition)
|
||||
{
|
||||
// modelType is not a closed generic type.
|
||||
return null;
|
||||
}
|
||||
var closedGenericInterface =
|
||||
ClosedGenericMatcher.ExtractGenericInterface(modelType, supportedInterfaceType);
|
||||
|
||||
var modelTypeArguments = modelTypeInfo.GenericTypeArguments;
|
||||
if (modelTypeArguments.Length != supportedInterfaceType.GetTypeInfo().GenericTypeParameters.Length)
|
||||
{
|
||||
// Wrong number of generic type arguments.
|
||||
return null;
|
||||
}
|
||||
|
||||
var closedInstanceType = supportedInterfaceType.MakeGenericType(modelTypeArguments);
|
||||
if (!closedInstanceType.IsAssignableFrom(modelType))
|
||||
{
|
||||
// modelType is not compatible with supportedInterfaceType.
|
||||
return null;
|
||||
}
|
||||
|
||||
return modelTypeArguments;
|
||||
return closedGenericInterface?.GenericTypeArguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.Empty(Assert.IsType<int[]>(modelBindingResult.Model));
|
||||
|
||||
Assert.Equal(0, modelState.Count);
|
||||
Assert.Empty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
|
@ -330,7 +330,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.Empty(Assert.IsType<Person[]>(modelBindingResult.Model));
|
||||
|
||||
Assert.Equal(0, modelState.Count);
|
||||
Assert.Empty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
|
|
@ -59,8 +60,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal("11", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsListOfSimpleType_WithExplicitPrefix_Success()
|
||||
[Theory]
|
||||
[InlineData("?prefix[0]=10&prefix[1]=11")]
|
||||
[InlineData("?prefix.index=low&prefix.index=high&prefix[low]=10&prefix[high]=11")]
|
||||
public async Task CollectionModelBinder_BindsListOfSimpleType_WithExplicitPrefix_Success(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
|
|
@ -76,7 +79,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?prefix[0]=10&prefix[1]=11");
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
|
@ -94,18 +97,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0]").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[1]").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsCollectionOfSimpleType_EmptyPrefix_Success()
|
||||
[Theory]
|
||||
[InlineData("?[0]=10&[1]=11")]
|
||||
[InlineData("?index=low&index=high&[high]=11&[low]=10")]
|
||||
public async Task CollectionModelBinder_BindsCollectionOfSimpleType_EmptyPrefix_Success(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
|
|
@ -117,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?[0]=10&[1]=11");
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
|
@ -135,14 +132,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "[0]").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "[1]").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -171,7 +160,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.Empty(Assert.IsType<List<int>>(modelBindingResult.Model));
|
||||
|
||||
Assert.Equal(0, modelState.Count);
|
||||
Assert.Empty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
|
@ -181,8 +170,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsListOfComplexType_WithPrefix_Success()
|
||||
[Theory]
|
||||
[InlineData("?[0].Id=10&[1].Id=11")]
|
||||
[InlineData("?index=low&index=high&[low].Id=10&[high].Id=11")]
|
||||
[InlineData("?parameter[0].Id=10¶meter[1].Id=11")]
|
||||
[InlineData("?parameter.index=low¶meter.index=high¶meter[low].Id=10¶meter[high].Id=11")]
|
||||
public async Task CollectionModelBinder_BindsListOfComplexType_ImpliedPrefix_Success(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
|
|
@ -194,7 +187,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?parameter[0].Id=10¶meter[1].Id=11");
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
|
@ -213,18 +206,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[0].Id").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Id").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsListOfComplexType_WithExplicitPrefix_Success()
|
||||
[Theory]
|
||||
[InlineData("?prefix[0].Id=10&prefix[1].Id=11")]
|
||||
[InlineData("?prefix.index=low&prefix.index=high&prefix[high].Id=11&prefix[low].Id=10")]
|
||||
public async Task CollectionModelBinder_BindsListOfComplexType_ExplicitPrefix_Success(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
|
|
@ -240,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?prefix[0].Id=10&prefix[1].Id=11");
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
|
@ -259,56 +246,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[0].Id").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "prefix[1].Id").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsCollectionOfComplexType_EmptyPrefix_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(List<Person>)
|
||||
};
|
||||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?[0].Id=10&[1].Id=11");
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<List<Person>>(modelBindingResult.Model);
|
||||
Assert.Equal(10, model[0].Id);
|
||||
Assert.Equal(11, model[1].Id);
|
||||
|
||||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "[0].Id").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "[1].Id").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -337,7 +274,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.Empty(Assert.IsType<List<Person>>(modelBindingResult.Model));
|
||||
|
||||
Assert.Equal(0, modelState.Count);
|
||||
Assert.Empty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
|
@ -514,6 +451,103 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsListOfSimpleType_WithIndex_Success()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(List<int>)
|
||||
};
|
||||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString =
|
||||
new QueryString("?parameter.index=low¶meter.index=high¶meter[low]=10¶meter[high]=11");
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<List<int>>(modelBindingResult.Model);
|
||||
Assert.Equal(new List<int>() { 10, 11 }, model);
|
||||
|
||||
// "index" is not stored in ModelState.
|
||||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low]").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[high]").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsCollectionOfComplexType_WithRequiredProperty_WithIndex_PartialData()
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(ICollection<Person2>)
|
||||
};
|
||||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString("?index=low&index=high&[high].Id=11&[low].Id=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<List<Person2>>(modelBindingResult.Model);
|
||||
Assert.Equal(10, model[0].Id);
|
||||
Assert.Null(model[0].Name);
|
||||
Assert.Equal(11, model[1].Id);
|
||||
Assert.Null(model[1].Name);
|
||||
|
||||
Assert.Equal(4, modelState.Count);
|
||||
Assert.Equal(2, modelState.ErrorCount);
|
||||
Assert.False(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "[low].Id").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "[high].Id").Value;
|
||||
Assert.Equal("11", entry.AttemptedValue);
|
||||
Assert.Equal("11", entry.RawValue);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "[low].Name").Value;
|
||||
Assert.Null(entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "[high].Name").Value;
|
||||
Assert.Null(entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollectionModelBinder_BindsListOfComplexType_WithRequiredProperty_NoData()
|
||||
{
|
||||
|
|
@ -540,7 +574,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.Empty(Assert.IsType<List<Person2>>(modelBindingResult.Model));
|
||||
|
||||
Assert.Equal(0, modelState.Count);
|
||||
Assert.Empty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
|
@ -649,13 +683,567 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
Assert.IsType<Person5>(modelBindingResult.Model);
|
||||
|
||||
Assert.Equal(1, modelState.Count);
|
||||
Assert.Equal(1, modelState.ErrorCount);
|
||||
Assert.False(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "Addresses[Key1].Street").Value;
|
||||
var kvp = Assert.Single(modelState);
|
||||
Assert.Equal("Addresses[Key1].Street", kvp.Key);
|
||||
var entry = kvp.Value;
|
||||
var error = Assert.Single(entry.Errors);
|
||||
Assert.Equal("The field Street must be a string with a maximum length of 3.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("?[0].Street=LongStreet")]
|
||||
[InlineData("?index=low&[low].Street=LongStreet")]
|
||||
[InlineData("?parameter[0].Street=LongStreet")]
|
||||
[InlineData("?parameter.index=low¶meter[low].Street=LongStreet")]
|
||||
public async Task CollectionModelBinder_BindsCollectionOfComplexType_ImpliedPrefix_FindsValidationErrors(
|
||||
string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(ICollection<Address5>),
|
||||
};
|
||||
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
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<List<Address5>>(modelBindingResult.Model);
|
||||
var address = Assert.Single(model);
|
||||
Assert.Equal("LongStreet", address.Street);
|
||||
|
||||
Assert.False(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState).Value;
|
||||
var error = Assert.Single(entry.Errors);
|
||||
Assert.Equal("The field Street must be a string with a maximum length of 3.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
// parameter type, form content, expected type
|
||||
public static TheoryData<Type, IDictionary<string, string[]>, Type> CollectionTypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<Type, IDictionary<string, string[]>, Type>
|
||||
{
|
||||
{
|
||||
typeof(IEnumerable<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[0]", new[] { "hello" } },
|
||||
{ "[1]", new[] { "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(ICollection<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "index", new[] { "low", "high" } },
|
||||
{ "[low]", new[] { "hello" } },
|
||||
{ "[high]", new[] { "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(IList<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[0]", new[] { "hello" } },
|
||||
{ "[1]", new[] { "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(List<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "index", new[] { "low", "high" } },
|
||||
{ "[low]", new[] { "hello" } },
|
||||
{ "[high]", new[] { "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericCollection),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[0]", new[] { "hello" } },
|
||||
{ "[1]", new[] { "world" } },
|
||||
},
|
||||
typeof(ClosedGenericCollection)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericList),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "index", new[] { "low", "high" } },
|
||||
{ "[low]", new[] { "hello" } },
|
||||
{ "[high]", new[] { "world" } },
|
||||
},
|
||||
typeof(ClosedGenericList)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitClosedGenericCollection),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[0]", new[] { "hello" } },
|
||||
{ "[1]", new[] { "world" } },
|
||||
},
|
||||
typeof(ExplicitClosedGenericCollection)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitClosedGenericList),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "index", new[] { "low", "high" } },
|
||||
{ "[low]", new[] { "hello" } },
|
||||
{ "[high]", new[] { "world" } },
|
||||
},
|
||||
typeof(ExplicitClosedGenericList)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitCollection<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[0]", new[] { "hello" } },
|
||||
{ "[1]", new[] { "world" } },
|
||||
},
|
||||
typeof(ExplicitCollection<string>)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitList<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "index", new[] { "low", "high" } },
|
||||
{ "[low]", new[] { "hello" } },
|
||||
{ "[high]", new[] { "world" } },
|
||||
},
|
||||
typeof(ExplicitList<string>)
|
||||
},
|
||||
{
|
||||
typeof(IEnumerable<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ string.Empty, new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(ICollection<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[]", new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(IList<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ string.Empty, new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(List<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[]", new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(List<string>)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericCollection),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ string.Empty, new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(ClosedGenericCollection)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericList),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[]", new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(ClosedGenericList)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitClosedGenericCollection),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ string.Empty, new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(ExplicitClosedGenericCollection)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitClosedGenericList),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[]", new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(ExplicitClosedGenericList)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitCollection<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ string.Empty, new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(ExplicitCollection<string>)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitList<string>),
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
{ "[]", new[] { "hello", "world" } },
|
||||
},
|
||||
typeof(ExplicitList<string>)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CollectionTypeData))]
|
||||
public async Task CollectionModelBinder_BindsParameterToExpectedType(
|
||||
Type parameterType,
|
||||
IDictionary<string, string[]> formContent,
|
||||
Type expectedType)
|
||||
{
|
||||
// Arrange
|
||||
var expectedCollection = new List<string> { "hello", "world" };
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = parameterType,
|
||||
};
|
||||
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var modelState = new ModelStateDictionary();
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.Form = new FormCollection(formContent);
|
||||
});
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
Assert.IsType(expectedType, modelBindingResult.Model);
|
||||
|
||||
var model = modelBindingResult.Model as IEnumerable<string>;
|
||||
Assert.NotNull(model); // Guard
|
||||
Assert.Equal(expectedCollection, model);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.NotEmpty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
}
|
||||
|
||||
private class ClosedGenericCollection : Collection<string>
|
||||
{
|
||||
}
|
||||
|
||||
private class ClosedGenericList : List<string>
|
||||
{
|
||||
}
|
||||
|
||||
private class ExplicitClosedGenericCollection : ICollection<string>
|
||||
{
|
||||
private List<string> _data = new List<string>();
|
||||
|
||||
int ICollection<string>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<string>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<string>.Add(string item)
|
||||
{
|
||||
_data.Add(item);
|
||||
}
|
||||
|
||||
void ICollection<string>.Clear()
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<string>.Contains(string item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<string>.CopyTo(string[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_data).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<string> IEnumerable<string>.GetEnumerator()
|
||||
{
|
||||
return _data.GetEnumerator();
|
||||
}
|
||||
|
||||
bool ICollection<string>.Remove(string item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ExplicitClosedGenericList : IList<string>
|
||||
{
|
||||
private List<string> _data = new List<string>();
|
||||
|
||||
string IList<string>.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<string>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<string>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<string>.Add(string item)
|
||||
{
|
||||
_data.Add(item);
|
||||
}
|
||||
|
||||
void ICollection<string>.Clear()
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<string>.Contains(string item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<string>.CopyTo(string[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_data).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<string> IEnumerable<string>.GetEnumerator()
|
||||
{
|
||||
return _data.GetEnumerator();
|
||||
}
|
||||
|
||||
int IList<string>.IndexOf(string item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IList<string>.Insert(int index, string item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool ICollection<string>.Remove(string item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IList<string>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ExplicitCollection<T> : ICollection<T>
|
||||
{
|
||||
private List<T> _data = new List<T>();
|
||||
|
||||
int ICollection<T>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<T>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
_data.Add(item);
|
||||
}
|
||||
|
||||
void ICollection<T>.Clear()
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Contains(T item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_data).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return _data.GetEnumerator();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ExplicitList<T> : IList<T>
|
||||
{
|
||||
private List<T> _data = new List<T>();
|
||||
|
||||
T IList<T>.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<T>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<T>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
_data.Add(item);
|
||||
}
|
||||
|
||||
void ICollection<T>.Clear()
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Contains(T item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_data).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return _data.GetEnumerator();
|
||||
}
|
||||
|
||||
int IList<T>.IndexOf(T item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
|
@ -91,9 +94,57 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal("10", entry.RawValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithIndex_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.index=low¶meter[low].Key=key0¶meter[low].Value=10");
|
||||
});
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Dictionary<string, int>>(modelBindingResult.Model);
|
||||
Assert.Equal(new Dictionary<string, int>() { { "key0", 10 } }, model);
|
||||
|
||||
// "index" is not stored in ModelState.
|
||||
Assert.Equal(2, modelState.Count);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low].Key").Value;
|
||||
Assert.Equal("key0", entry.AttemptedValue);
|
||||
Assert.Equal("key0", entry.RawValue);
|
||||
|
||||
// Key and Value are skipped if they have simple types.
|
||||
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
|
||||
|
||||
entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[low].Value").Value;
|
||||
Assert.Equal("10", entry.AttemptedValue);
|
||||
Assert.Equal("10", entry.RawValue);
|
||||
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("?prefix[key0]=10")]
|
||||
[InlineData("?prefix[0].Key=key0&prefix[0].Value=10")]
|
||||
[InlineData("?prefix.index=low&prefix[low].Key=key0&prefix[low].Value=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithExplicitPrefix_Success(
|
||||
string queryString)
|
||||
{
|
||||
|
|
@ -134,6 +185,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
[Theory]
|
||||
[InlineData("?[key0]=10")]
|
||||
[InlineData("?[0].Key=key0&[0].Value=10")]
|
||||
[InlineData("?index=low&[low].Key=key0&[low].Value=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -201,6 +253,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
|
||||
private class Person
|
||||
{
|
||||
[Range(minimum: 0, maximum: 15, ErrorMessage = "You're out of range.")]
|
||||
public int Id { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
|
|
@ -222,9 +275,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("?[key0].Id=10")]
|
||||
[InlineData("?[0].Key=key0&[0].Value.Id=10")]
|
||||
[InlineData("?index=low&[low].Key=key0&[low].Value.Id=10")]
|
||||
[InlineData("?parameter[key0].Id=10")]
|
||||
[InlineData("?parameter[0].Key=key0¶meter[0].Value.Id=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithPrefix_Success(string queryString)
|
||||
[InlineData("?parameter.index=low¶meter[low].Key=key0¶meter[low].Value.Id=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ImpliedPrefix_Success(string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
|
|
@ -259,7 +316,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
[Theory]
|
||||
[InlineData("?prefix[key0].Id=10")]
|
||||
[InlineData("?prefix[0].Key=key0&prefix[0].Value.Id=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_WithExplicitPrefix_Success(
|
||||
[InlineData("?prefix.index=low&prefix[low].Key=key0&prefix[low].Value.Id=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ExplicitPrefix_Success(
|
||||
string queryString)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -297,9 +355,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("?[key0].Id=10")]
|
||||
[InlineData("?[0].Key=key0&[0].Value.Id=10")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_EmptyPrefix_Success(string queryString)
|
||||
[InlineData("?[key0].Id=100")]
|
||||
[InlineData("?[0].Key=key0&[0].Value.Id=100")]
|
||||
[InlineData("?index=low&[low].Key=key0&[low].Value.Id=100")]
|
||||
[InlineData("?parameter[key0].Id=100")]
|
||||
[InlineData("?parameter[0].Key=key0¶meter[0].Value.Id=100")]
|
||||
[InlineData("?parameter.index=low¶meter[low].Key=key0¶meter[low].Value.Id=100")]
|
||||
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ImpliedPrefix_FindsValidationErrors(
|
||||
string queryString)
|
||||
{
|
||||
// Arrange
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
|
|
@ -324,11 +387,19 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Dictionary<string, Person>>(modelBindingResult.Model);
|
||||
Assert.Equal(new Dictionary<string, Person> { { "key0", new Person { Id = 10 } }, }, model);
|
||||
Assert.Equal(new Dictionary<string, Person> { { "key0", new Person { Id = 100 } }, }, model);
|
||||
|
||||
Assert.NotEmpty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.False(modelState.IsValid);
|
||||
Assert.All(modelState, kvp =>
|
||||
{
|
||||
Assert.NotEqual(ModelValidationState.Unvalidated, kvp.Value.ValidationState);
|
||||
Assert.NotEqual(ModelValidationState.Skipped, kvp.Value.ValidationState);
|
||||
});
|
||||
|
||||
var entry = Assert.Single(modelState, kvp => kvp.Value.ValidationState == ModelValidationState.Invalid);
|
||||
var error = Assert.Single(entry.Value.Errors);
|
||||
Assert.Equal("You're out of range.", error.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -363,5 +434,339 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
|
|||
Assert.Equal(0, modelState.ErrorCount);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
// parameter type, query string, expected type
|
||||
public static TheoryData<Type, string, Type> DictionaryTypeData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<Type, string, Type>
|
||||
{
|
||||
{
|
||||
typeof(IDictionary<string, string>),
|
||||
"?[key0]=hello&[key1]=world",
|
||||
typeof(Dictionary<string, string>)
|
||||
},
|
||||
{
|
||||
typeof(Dictionary<string, string>),
|
||||
"?[key0]=hello&[key1]=world",
|
||||
typeof(Dictionary<string, string>)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericDictionary),
|
||||
"?[key0]=hello&[key1]=world",
|
||||
typeof(ClosedGenericDictionary)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericKeyDictionary<string>),
|
||||
"?[key0]=hello&[key1]=world",
|
||||
typeof(ClosedGenericKeyDictionary<string>)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitClosedGenericDictionary),
|
||||
"?[key0]=hello&[key1]=world",
|
||||
typeof(ExplicitClosedGenericDictionary)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitDictionary<string, string>),
|
||||
"?[key0]=hello&[key1]=world",
|
||||
typeof(ExplicitDictionary<string, string>)
|
||||
},
|
||||
{
|
||||
typeof(IDictionary<string, string>),
|
||||
"?index=low&index=high&[low].Key=key0&[low].Value=hello&[high].Key=key1&[high].Value=world",
|
||||
typeof(Dictionary<string, string>)
|
||||
},
|
||||
{
|
||||
typeof(Dictionary<string, string>),
|
||||
"?[0].Key=key0&[0].Value=hello&[1].Key=key1&[1].Value=world",
|
||||
typeof(Dictionary<string, string>)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericDictionary),
|
||||
"?index=low&index=high&[low].Key=key0&[low].Value=hello&[high].Key=key1&[high].Value=world",
|
||||
typeof(ClosedGenericDictionary)
|
||||
},
|
||||
{
|
||||
typeof(ClosedGenericKeyDictionary<string>),
|
||||
"?[0].Key=key0&[0].Value=hello&[1].Key=key1&[1].Value=world",
|
||||
typeof(ClosedGenericKeyDictionary<string>)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitClosedGenericDictionary),
|
||||
"?index=low&index=high&[low].Key=key0&[low].Value=hello&[high].Key=key1&[high].Value=world",
|
||||
typeof(ExplicitClosedGenericDictionary)
|
||||
},
|
||||
{
|
||||
typeof(ExplicitDictionary<string, string>),
|
||||
"?[0].Key=key0&[0].Value=hello&[1].Key=key1&[1].Value=world",
|
||||
typeof(ExplicitDictionary<string, string>)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DictionaryTypeData))]
|
||||
public async Task DictionaryModelBinder_BindsParameterToExpectedType(
|
||||
Type parameterType,
|
||||
string queryString,
|
||||
Type expectedType)
|
||||
{
|
||||
// Arrange
|
||||
var expectedDictionary = new Dictionary<string, string>
|
||||
{
|
||||
{ "key0", "hello" },
|
||||
{ "key1", "world" },
|
||||
};
|
||||
var parameter = new ParameterDescriptor
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = parameterType,
|
||||
};
|
||||
|
||||
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
|
||||
var modelState = new ModelStateDictionary();
|
||||
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
|
||||
{
|
||||
request.QueryString = new QueryString(queryString);
|
||||
});
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(modelBindingResult);
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
Assert.IsType(expectedType, modelBindingResult.Model);
|
||||
|
||||
var model = modelBindingResult.Model as IDictionary<string, string>;
|
||||
Assert.NotNull(model); // Guard
|
||||
Assert.Equal(expectedDictionary.Keys, model.Keys);
|
||||
Assert.Equal(expectedDictionary.Values, model.Values);
|
||||
|
||||
Assert.True(modelState.IsValid);
|
||||
Assert.NotEmpty(modelState);
|
||||
Assert.Equal(0, modelState.ErrorCount);
|
||||
}
|
||||
|
||||
private class ClosedGenericDictionary : Dictionary<string, string>
|
||||
{
|
||||
}
|
||||
|
||||
private class ClosedGenericKeyDictionary<TValue> : Dictionary<string, TValue>
|
||||
{
|
||||
}
|
||||
|
||||
private class ExplicitClosedGenericDictionary : IDictionary<string, string>
|
||||
{
|
||||
private IDictionary<string, string> _data = new Dictionary<string, string>();
|
||||
|
||||
string IDictionary<string, string>.this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_data[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<KeyValuePair<string, string>>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Count;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string>>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<string> IDictionary<string, string>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<string> IDictionary<string, string>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Values;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item)
|
||||
{
|
||||
_data.Add(item);
|
||||
}
|
||||
|
||||
void IDictionary<string, string>.Add(string key, string value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string>>.Clear()
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<string, string>.ContainsKey(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<string, string>.Remove(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<string, string>.TryGetValue(string key, out string value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ExplicitDictionary<TKey, TValue> : IDictionary<TKey, TValue>
|
||||
{
|
||||
private IDictionary<TKey, TValue> _data = new Dictionary<TKey, TValue>();
|
||||
|
||||
TValue IDictionary<TKey, TValue>.this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_data[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<KeyValuePair<TKey, TValue>>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Count;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Values;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
_data.Add(item);
|
||||
}
|
||||
|
||||
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.Remove(TKey key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
|
|
|||
Loading…
Reference in New Issue