Rename `ModelMetadata.IsCollectionType` and add "real" `ModelMetadata.IsCollectionType`
- #3022 - existing `IsCollectionType` -> `IsEnumerableType` - use new `IsCollectionType` in a few places
This commit is contained in:
parent
1574637a6a
commit
d8d0a1ab89
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// <see cref="ModelMetadata"/> for <c>T</c> if <see cref="ModelType"/> implements
|
/// <see cref="ModelMetadata"/> for <c>T</c> if <see cref="ModelType"/> implements
|
||||||
/// <see cref="IEnumerable{T}"/>. <see cref="ModelMetadata"/> for <c>object</c> if <see cref="ModelType"/>
|
/// <see cref="IEnumerable{T}"/>. <see cref="ModelMetadata"/> for <c>object</c> if <see cref="ModelType"/>
|
||||||
/// implements <see cref="IEnumerable"/> but not <see cref="IEnumerable{T}"/>. <c>null</c> otherwise i.e. when
|
/// implements <see cref="IEnumerable"/> but not <see cref="IEnumerable{T}"/>. <c>null</c> otherwise i.e. when
|
||||||
/// <see cref="IsCollectionType"/> is <c>false</c>.
|
/// <see cref="IsEnumerableType"/> is <c>false</c>.
|
||||||
/// </value>
|
/// </value>
|
||||||
public abstract ModelMetadata ElementMetadata { get; }
|
public abstract ModelMetadata ElementMetadata { get; }
|
||||||
|
|
||||||
|
|
@ -322,10 +322,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// Gets a value indicating whether or not <see cref="ModelType"/> is a collection type.
|
/// Gets a value indicating whether or not <see cref="ModelType"/> is a collection type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A collection type is defined as a <see cref="Type"/> which is assignable to
|
/// A collection type is defined as a <see cref="Type"/> which is assignable to <see cref="ICollection{T}"/>.
|
||||||
/// <see cref="IEnumerable"/>, and is not a <see cref="string"/>.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool IsCollectionType
|
public bool IsCollectionType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Ignore non-generic ICollection type. Would require an additional check and that interface is not
|
||||||
|
// used in MVC.
|
||||||
|
var collectionType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(ICollection<>));
|
||||||
|
|
||||||
|
return collectionType != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not <see cref="ModelType"/> is an enumerable type.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// An enumerable type is defined as a <see cref="Type"/> which is assignable to
|
||||||
|
/// <see cref="IEnumerable"/>, and is not a <see cref="string"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsEnumerableType
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Routing": "1.0.0-*",
|
"Microsoft.AspNet.Routing": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.ClosedGenericMatcher.Sources": {
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"type": "build"
|
||||||
|
},
|
||||||
"Microsoft.Framework.CopyOnWriteDictionary.Sources": {
|
"Microsoft.Framework.CopyOnWriteDictionary.Sources": {
|
||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ namespace Microsoft.AspNet.Mvc.ApiExplorer
|
||||||
//
|
//
|
||||||
// 3) Types with no properties. Obviously nothing to explore there.
|
// 3) Types with no properties. Obviously nothing to explore there.
|
||||||
//
|
//
|
||||||
if (modelMetadata.IsCollectionType ||
|
if (modelMetadata.IsEnumerableType ||
|
||||||
!modelMetadata.IsComplexType ||
|
!modelMetadata.IsComplexType ||
|
||||||
!modelMetadata.Properties.Any())
|
!modelMetadata.Properties.Any())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -124,13 +124,11 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
var propertyHelper = propertyHelpers.First(helper =>
|
var propertyHelper = propertyHelpers.First(helper =>
|
||||||
string.Equals(helper.Name, property.Key, StringComparison.Ordinal));
|
string.Equals(helper.Name, property.Key, StringComparison.Ordinal));
|
||||||
var propertyType = propertyHelper.Property.PropertyType;
|
var propertyType = propertyHelper.Property.PropertyType;
|
||||||
|
var metadata = _modelMetadataProvider.GetMetadataForType(propertyType);
|
||||||
var source = property.Value;
|
var source = property.Value;
|
||||||
if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPublic == true)
|
if (propertyHelper.Property.CanWrite && propertyHelper.Property.SetMethod?.IsPublic == true)
|
||||||
{
|
{
|
||||||
// Handle settable property.
|
// Handle settable property. Do not set the property to null if the type is a non-nullable type.
|
||||||
var metadata = _modelMetadataProvider.GetMetadataForType(propertyType);
|
|
||||||
|
|
||||||
// Do not set the property to null if the type is a non-nullable type.
|
|
||||||
if (source != null || metadata.IsReferenceOrNullableType)
|
if (source != null || metadata.IsReferenceOrNullableType)
|
||||||
{
|
{
|
||||||
propertyHelper.SetValue(controller, source);
|
propertyHelper.SetValue(controller, source);
|
||||||
|
|
@ -153,12 +151,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine T if this is an ICollection<T> property.
|
if (!metadata.IsCollectionType)
|
||||||
var collectionTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
|
|
||||||
propertyType,
|
|
||||||
typeof(ICollection<>))
|
|
||||||
?.GenericTypeArguments;
|
|
||||||
if (collectionTypeArguments == null)
|
|
||||||
{
|
{
|
||||||
// Not a collection model.
|
// Not a collection model.
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -166,7 +159,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
|
|
||||||
// Handle a read-only collection property.
|
// Handle a read-only collection property.
|
||||||
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
||||||
collectionTypeArguments);
|
metadata.ElementMetadata.ModelType);
|
||||||
propertyAddRange.Invoke(obj: null, parameters: new[] { target, source });
|
propertyAddRange.Invoke(obj: null, parameters: new[] { target, source });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// ICollection<T>. For example an IEnumerable<T> property may have a List<T> default value. Do not use
|
// 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
|
// IsAssignableFrom() because that does not handle explicit interface implementations and binders all
|
||||||
// perform explicit casts.
|
// perform explicit casts.
|
||||||
var closedGenericInterface =
|
if (!context.ModelMetadata.IsCollectionType)
|
||||||
ClosedGenericMatcher.ExtractGenericInterface(context.Model.GetType(), typeof(ICollection<>));
|
|
||||||
if (closedGenericInterface == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,10 +236,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
if (!_haveCalculatedElementMetadata)
|
if (!_haveCalculatedElementMetadata)
|
||||||
{
|
{
|
||||||
_haveCalculatedElementMetadata = true;
|
_haveCalculatedElementMetadata = true;
|
||||||
if (!IsCollectionType)
|
if (!IsEnumerableType)
|
||||||
{
|
{
|
||||||
// Short-circuit checks below. If not IsCollectionType, ElementMetadata is null.
|
// Short-circuit checks below. If not IsEnumerableType, ElementMetadata is null.
|
||||||
// For example, as in IsCollectionType, do not consider strings collections.
|
// For example, as in IsEnumerableType, do not consider strings collections.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +261,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
|
|
||||||
Debug.Assert(
|
Debug.Assert(
|
||||||
elementType != null,
|
elementType != null,
|
||||||
$"Unable to find element type for '{ ModelType.FullName }' though IsCollectionType is true.");
|
$"Unable to find element type for '{ ModelType.FullName }' though IsEnumerableType is true.");
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
_elementMetadata = _provider.GetMetadataForType(elementType);
|
_elementMetadata = _provider.GetMetadataForType(elementType);
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelMetadata.IsCollectionType)
|
if (modelMetadata.IsEnumerableType)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -522,17 +522,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
// Determine T if this is an ICollection<T> property. No need for a T[] case because CanUpdateProperty()
|
// Determine T if this is an ICollection<T> property. No need for a T[] case because CanUpdateProperty()
|
||||||
// ensures property is either settable or not an array. Underlying assumption is that CanUpdateProperty()
|
// ensures property is either settable or not an array. Underlying assumption is that CanUpdateProperty()
|
||||||
// and SetProperty() are overridden together.
|
// and SetProperty() are overridden together.
|
||||||
var collectionTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
|
if (!propertyExplorer.Metadata.IsCollectionType)
|
||||||
propertyExplorer.ModelType,
|
|
||||||
typeof(ICollection<>))
|
|
||||||
?.GenericTypeArguments;
|
|
||||||
if (collectionTypeArguments == null)
|
|
||||||
{
|
{
|
||||||
// Not a collection model.
|
// Not a collection model.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(collectionTypeArguments);
|
var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod(
|
||||||
|
propertyExplorer.Metadata.ElementMetadata.ModelType);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
propertyAddRange.Invoke(obj: null, parameters: new[] { target, source });
|
propertyAddRange.Invoke(obj: null, parameters: new[] { target, source });
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||||
|
|
||||||
// Base allowMultiple on the instance or declared type of the expression to avoid a
|
// Base allowMultiple on the instance or declared type of the expression to avoid a
|
||||||
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
|
// "SelectExpressionNotEnumerable" InvalidOperationException during generation.
|
||||||
// Metadata.IsCollectionType() is similar but does not take runtime type into account.
|
// Metadata.IsEnumerableType is similar but does not take runtime type into account.
|
||||||
var realModelType = For.ModelExplorer.ModelType;
|
var realModelType = For.ModelExplorer.ModelType;
|
||||||
var allowMultiple = typeof(string) != realModelType &&
|
var allowMultiple = typeof(string) != realModelType &&
|
||||||
typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(realModelType.GetTypeInfo());
|
typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(realModelType.GetTypeInfo());
|
||||||
|
|
|
||||||
|
|
@ -818,7 +818,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
modelExplorer = modelExplorer ??
|
modelExplorer = modelExplorer ??
|
||||||
ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
|
ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
|
||||||
var metadata = modelExplorer.Metadata;
|
var metadata = modelExplorer.Metadata;
|
||||||
if (allowMultiple && metadata.IsCollectionType)
|
if (allowMultiple && metadata.IsEnumerableType)
|
||||||
{
|
{
|
||||||
metadata = metadata.ElementMetadata;
|
metadata = metadata.ElementMetadata;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.True(modelMetadata.IsComplexType);
|
Assert.True(modelMetadata.IsComplexType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCollectionType
|
// IsCollectionType / IsEnumerableType
|
||||||
|
|
||||||
private class NonCollectionType
|
private class NonCollectionType
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -59,11 +60,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class JustEnumerable : IEnumerable
|
||||||
|
{
|
||||||
|
public IEnumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<Type> NonCollectionNonEnumerableData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<Type>
|
||||||
|
{
|
||||||
|
typeof(object),
|
||||||
|
typeof(int),
|
||||||
|
typeof(NonCollectionType),
|
||||||
|
typeof(string),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TheoryData<Type> CollectionAndEnumerableData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<Type>
|
||||||
|
{
|
||||||
|
typeof(int[]),
|
||||||
|
typeof(List<string>),
|
||||||
|
typeof(DerivedList),
|
||||||
|
typeof(Collection<int>),
|
||||||
|
typeof(Dictionary<object, object>),
|
||||||
|
typeof(CollectionImplementation),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(typeof(object))]
|
[MemberData(nameof(NonCollectionNonEnumerableData))]
|
||||||
[InlineData(typeof(int))]
|
[InlineData(typeof(IEnumerable))]
|
||||||
[InlineData(typeof(NonCollectionType))]
|
[InlineData(typeof(IEnumerable<string>))]
|
||||||
[InlineData(typeof(string))]
|
[InlineData(typeof(JustEnumerable))]
|
||||||
public void IsCollectionType_ReturnsFalseForNonCollectionTypes(Type type)
|
public void IsCollectionType_ReturnsFalseForNonCollectionTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -77,13 +116,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(typeof(int[]))]
|
[MemberData(nameof(CollectionAndEnumerableData))]
|
||||||
[InlineData(typeof(List<string>))]
|
|
||||||
[InlineData(typeof(DerivedList))]
|
|
||||||
[InlineData(typeof(IEnumerable))]
|
|
||||||
[InlineData(typeof(IEnumerable<string>))]
|
|
||||||
[InlineData(typeof(Collection<int>))]
|
|
||||||
[InlineData(typeof(Dictionary<object, object>))]
|
|
||||||
public void IsCollectionType_ReturnsTrueForCollectionTypes(Type type)
|
public void IsCollectionType_ReturnsTrueForCollectionTypes(Type type)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -96,6 +129,37 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
Assert.True(modelMetadata.IsCollectionType);
|
Assert.True(modelMetadata.IsCollectionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(NonCollectionNonEnumerableData))]
|
||||||
|
public void IsEnumerableType_ReturnsFalseForNonEnumerableTypes(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(modelMetadata.IsEnumerableType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(CollectionAndEnumerableData))]
|
||||||
|
[InlineData(typeof(IEnumerable))]
|
||||||
|
[InlineData(typeof(IEnumerable<string>))]
|
||||||
|
[InlineData(typeof(JustEnumerable))]
|
||||||
|
public void IsEnumerableType_ReturnsTrueForEnumerableTypes(Type type)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var modelMetadata = new TestModelMetadata(type);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(modelMetadata.IsEnumerableType);
|
||||||
|
}
|
||||||
|
|
||||||
// IsNullableValueType
|
// IsNullableValueType
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -481,5 +545,59 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CollectionImplementation : ICollection<string>
|
||||||
|
{
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(string item)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(string item)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(string[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<string> GetEnumerator()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(string item)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
Assert.True(metadata.HtmlEncode);
|
Assert.True(metadata.HtmlEncode);
|
||||||
Assert.True(metadata.IsBindingAllowed);
|
Assert.True(metadata.IsBindingAllowed);
|
||||||
Assert.False(metadata.IsBindingRequired);
|
Assert.False(metadata.IsBindingRequired);
|
||||||
Assert.False(metadata.IsComplexType);
|
|
||||||
Assert.False(metadata.IsCollectionType);
|
Assert.False(metadata.IsCollectionType);
|
||||||
|
Assert.False(metadata.IsComplexType);
|
||||||
|
Assert.False(metadata.IsEnumerableType);
|
||||||
Assert.False(metadata.IsEnum);
|
Assert.False(metadata.IsEnum);
|
||||||
Assert.False(metadata.IsFlagsEnum);
|
Assert.False(metadata.IsFlagsEnum);
|
||||||
Assert.False(metadata.IsNullableValueType);
|
Assert.False(metadata.IsNullableValueType);
|
||||||
|
|
@ -218,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var isBindingRequired = metadata.IsBindingRequired;
|
var isBindingRequired = metadata.IsBindingRequired;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(isBindingRequired);
|
Assert.False(isBindingRequired);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue