Add `ModelMetadata.ElementMetadata`
- #2664 - use new property to correctly determine `isTargetEnum` in `GetCurrentValues()` - avoid `ArgumentNullException` in all cases where raw values are `enum` but target is not - stop skipping tests blocked by #2664, exposing a couple more #1487 issues - use new property instead of private `GetElementType()` methods where possible - cleans up some duplicate code - also remove redundant use of `IsCollectionType` and `ElementMetadata` nits: - move properties above methods in `ModelMetadata` - avoid accidentally-incorrect "Remove Unnecessary Usings"
This commit is contained in:
parent
afd5b4f2a6
commit
3f6ab3bb03
|
|
@ -5,7 +5,9 @@ using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
#if DNXCORE50
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
#endif
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
|
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
|
|
@ -79,7 +81,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public abstract string BinderModelName { get; }
|
public abstract string BinderModelName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="Type"/> of an <see cref="IModelBinder"/> of a model if specified explicitly using
|
/// Gets the <see cref="Type"/> of an <see cref="IModelBinder"/> of a model if specified explicitly using
|
||||||
/// <see cref="IBinderTypeProviderMetadata"/>.
|
/// <see cref="IBinderTypeProviderMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract Type BinderType { get; }
|
public abstract Type BinderType { get; }
|
||||||
|
|
@ -124,6 +126,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string EditFormatString { get; }
|
public abstract string EditFormatString { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="ModelMetadata"/> for elements of <see cref="ModelType"/> if that <see cref="Type"/>
|
||||||
|
/// implements <see cref="IEnumerable"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// <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"/>
|
||||||
|
/// implements <see cref="IEnumerable"/> but not <see cref="IEnumerable{T}"/>. <c>null</c> otherwise i.e. when
|
||||||
|
/// <see cref="IsCollectionType"/> is <c>false</c>.
|
||||||
|
/// </value>
|
||||||
|
public abstract ModelMetadata ElementMetadata { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ordered display names and values of all <see cref="Enum"/> values in <see cref="ModelType"/> or
|
/// Gets the ordered display names and values of all <see cref="Enum"/> values in <see cref="ModelType"/> or
|
||||||
/// <c>Nullable.GetUnderlyingType(ModelType)</c>.
|
/// <c>Nullable.GetUnderlyingType(ModelType)</c>.
|
||||||
|
|
@ -329,6 +343,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a property getter delegate to get the property value from a model object.
|
||||||
|
/// </summary>
|
||||||
|
public abstract Func<object, object> PropertyGetter { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a property setter delegate to set the property value on a model object.
|
||||||
|
/// </summary>
|
||||||
|
public abstract Action<object, object> PropertySetter { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a display name for the model.
|
/// Gets a display name for the model.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -341,15 +365,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
return DisplayName ?? PropertyName ?? ModelType.Name;
|
return DisplayName ?? PropertyName ?? ModelType.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a property getter delegate to get the property value from a model object.
|
|
||||||
/// </summary>
|
|
||||||
public abstract Func<object, object> PropertyGetter { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a property setter delegate to set the property value on a model object.
|
|
||||||
/// </summary>
|
|
||||||
public abstract Action<object, object> PropertySetter { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,14 @@
|
||||||
// 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;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
#if DNXCORE50
|
||||||
|
using System.Reflection;
|
||||||
|
#endif
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
|
|
@ -19,6 +24,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
private readonly DefaultMetadataDetails _details;
|
private readonly DefaultMetadataDetails _details;
|
||||||
|
|
||||||
private ReadOnlyDictionary<object, object> _additionalValues;
|
private ReadOnlyDictionary<object, object> _additionalValues;
|
||||||
|
private ModelMetadata _elementMetadata;
|
||||||
|
private bool _haveCalculatedElementMetadata;
|
||||||
private bool? _isReadOnly;
|
private bool? _isReadOnly;
|
||||||
private bool? _isRequired;
|
private bool? _isRequired;
|
||||||
private ModelPropertyCollection _properties;
|
private ModelPropertyCollection _properties;
|
||||||
|
|
@ -220,6 +227,49 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ModelMetadata ElementMetadata
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!_haveCalculatedElementMetadata)
|
||||||
|
{
|
||||||
|
_haveCalculatedElementMetadata = true;
|
||||||
|
if (!IsCollectionType)
|
||||||
|
{
|
||||||
|
// Short-circuit checks below. If not IsCollectionType, ElementMetadata is null.
|
||||||
|
// For example, as in IsCollectionType, do not consider strings collections.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type elementType = null;
|
||||||
|
if (ModelType.IsArray)
|
||||||
|
{
|
||||||
|
elementType = ModelType.GetElementType();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elementType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(IEnumerable<>))
|
||||||
|
?.GenericTypeArguments[0];
|
||||||
|
if (elementType == null && typeof(IEnumerable).IsAssignableFrom(ModelType))
|
||||||
|
{
|
||||||
|
// ModelType implements IEnumerable but not IEnumerable<T>.
|
||||||
|
elementType = typeof(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(
|
||||||
|
elementType != null,
|
||||||
|
$"Unable to find element type for '{ ModelType.FullName }' though IsCollectionType is true.");
|
||||||
|
|
||||||
|
// Success
|
||||||
|
_elementMetadata = _provider.GetMetadataForType(elementType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _elementMetadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues
|
public override IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -169,11 +169,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
ExpandValidationNode(validationContext, modelExplorer);
|
ExpandValidationNode(validationContext, modelExplorer);
|
||||||
|
|
||||||
IList<IModelValidator> validators = null;
|
IList<IModelValidator> validators = null;
|
||||||
if (modelExplorer.Metadata.IsCollectionType && modelExplorer.Model != null)
|
var elementMetadata = modelExplorer.Metadata.ElementMetadata;
|
||||||
|
if (elementMetadata != null)
|
||||||
{
|
{
|
||||||
var enumerableModel = (IEnumerable)modelExplorer.Model;
|
|
||||||
var elementType = GetElementType(enumerableModel.GetType());
|
|
||||||
var elementMetadata = _modelMetadataProvider.GetMetadataForType(elementType);
|
|
||||||
validators = GetValidators(validationContext.ModelValidationContext.ValidatorProvider, elementMetadata);
|
validators = GetValidators(validationContext.ModelValidationContext.ValidatorProvider, elementMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,7 +281,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!modelExplorer.Metadata.IsCollectionType)
|
var elementMetadata = modelExplorer.Metadata.ElementMetadata;
|
||||||
|
if (elementMetadata == null)
|
||||||
{
|
{
|
||||||
foreach (var property in validationNode.ModelMetadata.Properties)
|
foreach (var property in validationNode.ModelMetadata.Properties)
|
||||||
{
|
{
|
||||||
|
|
@ -300,8 +299,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var enumerableModel = (IEnumerable)modelExplorer.Model;
|
var enumerableModel = (IEnumerable)modelExplorer.Model;
|
||||||
var elementType = GetElementType(enumerableModel.GetType());
|
|
||||||
var elementMetadata = _modelMetadataProvider.GetMetadataForType(elementType);
|
|
||||||
|
|
||||||
// An integer index is incorrect in scenarios where there is a custom index provided by the user.
|
// An integer index is incorrect in scenarios where there is a custom index provided by the user.
|
||||||
// However those scenarios are supported by createing a ModelValidationNode with the right keys.
|
// However those scenarios are supported by createing a ModelValidationNode with the right keys.
|
||||||
|
|
@ -321,26 +318,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type GetElementType(Type type)
|
|
||||||
{
|
|
||||||
Debug.Assert(typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()));
|
|
||||||
if (type.IsArray)
|
|
||||||
{
|
|
||||||
return type.GetElementType();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var implementedInterface in type.GetInterfaces())
|
|
||||||
{
|
|
||||||
if (implementedInterface.GetTypeInfo().IsGenericType &&
|
|
||||||
implementedInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
|
||||||
{
|
|
||||||
return implementedInterface.GetGenericArguments()[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ValidationContext
|
private class ValidationContext
|
||||||
{
|
{
|
||||||
public ModelValidationContext ModelValidationContext { get; set; }
|
public ModelValidationContext ModelValidationContext { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// <param name="expressions">Expressions identifying the properties to allow for binding.</param>
|
/// <param name="expressions">Expressions identifying the properties to allow for binding.</param>
|
||||||
/// <returns>An expression which can be used with <see cref="IPropertyBindingPredicateProvider"/>.</returns>
|
/// <returns>An expression which can be used with <see cref="IPropertyBindingPredicateProvider"/>.</returns>
|
||||||
public static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>(
|
public static Expression<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>(
|
||||||
string prefix,
|
string prefix,
|
||||||
Expression<Func<TModel, object>>[] expressions)
|
Expression<Func<TModel, object>>[] expressions)
|
||||||
{
|
{
|
||||||
if (expressions.Length == 0)
|
if (expressions.Length == 0)
|
||||||
|
|
@ -417,15 +417,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// If modelkey is empty, we need to iterate through properties (obtained from ModelMetadata) and
|
// If modelkey is empty, we need to iterate through properties (obtained from ModelMetadata) and
|
||||||
// clear validation state for all entries in ModelStateDictionary that start with each property name.
|
// clear validation state for all entries in ModelStateDictionary that start with each property name.
|
||||||
// If modelkey is non-empty, clear validation state for all entries in ModelStateDictionary
|
// If modelkey is non-empty, clear validation state for all entries in ModelStateDictionary
|
||||||
// that start with modelKey
|
// that start with modelKey
|
||||||
if (string.IsNullOrEmpty(modelKey))
|
if (string.IsNullOrEmpty(modelKey))
|
||||||
{
|
{
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
|
||||||
if (modelMetadata.IsCollectionType)
|
var elementMetadata = modelMetadata.ElementMetadata;
|
||||||
|
if (elementMetadata != null)
|
||||||
{
|
{
|
||||||
var elementType = GetElementType(modelMetadata.ModelType);
|
modelMetadata = elementMetadata;
|
||||||
modelMetadata = metadataProvider.GetMetadataForType(elementType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var property in modelMetadata.Properties)
|
foreach (var property in modelMetadata.Properties)
|
||||||
|
|
@ -440,27 +440,6 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type GetElementType(Type type)
|
|
||||||
{
|
|
||||||
Debug.Assert(typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()));
|
|
||||||
if (type.IsArray)
|
|
||||||
{
|
|
||||||
return type.GetElementType();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var implementedInterface in type.GetInterfaces())
|
|
||||||
{
|
|
||||||
if (implementedInterface.GetTypeInfo().IsGenericType &&
|
|
||||||
implementedInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
|
||||||
{
|
|
||||||
return implementedInterface.GetGenericArguments()[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal static void ValidateBindingContext([NotNull] ModelBindingContext bindingContext)
|
internal static void ValidateBindingContext([NotNull] ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelMetadata == null)
|
if (bindingContext.ModelMetadata == null)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNet.Mvc.Extensions;
|
using Microsoft.AspNet.Mvc.Extensions;
|
||||||
|
|
@ -106,40 +107,42 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
"Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
|
"Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeInCollection = typeof(string);
|
var elementMetadata = htmlHelper.ViewData.ModelMetadata.ElementMetadata;
|
||||||
var genericEnumerableType = ClosedGenericMatcher.ExtractGenericInterface(
|
Debug.Assert(elementMetadata != null);
|
||||||
collection.GetType(),
|
var typeInCollectionIsNullableValueType = elementMetadata.IsNullableValueType;
|
||||||
typeof(IEnumerable<>));
|
|
||||||
if (genericEnumerableType != null)
|
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
|
||||||
|
var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();
|
||||||
|
|
||||||
|
// Use typeof(string) instead of typeof(object) for IEnumerable collections. Neither type is Nullable<T>.
|
||||||
|
if (elementMetadata.ModelType == typeof(object))
|
||||||
{
|
{
|
||||||
typeInCollection = genericEnumerableType.GenericTypeArguments[0];
|
elementMetadata = metadataProvider.GetMetadataForType(typeof(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeInCollectionIsNullableValueType = TypeHelper.IsNullableValueType(typeInCollection);
|
|
||||||
|
|
||||||
var oldPrefix = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix;
|
var oldPrefix = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
||||||
|
|
||||||
var fieldNameBase = oldPrefix;
|
var fieldNameBase = oldPrefix;
|
||||||
var result = new StringBuilder();
|
var result = new StringBuilder();
|
||||||
|
|
||||||
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
|
|
||||||
var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();
|
|
||||||
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var item in collection)
|
foreach (var item in collection)
|
||||||
{
|
{
|
||||||
var itemType = typeInCollection;
|
var itemMetadata = elementMetadata;
|
||||||
if (item != null && !typeInCollectionIsNullableValueType)
|
if (item != null && !typeInCollectionIsNullableValueType)
|
||||||
{
|
{
|
||||||
itemType = item.GetType();
|
itemMetadata = metadataProvider.GetMetadataForType(item.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = metadataProvider.GetModelExplorerForType(itemType, item);
|
var modelExplorer = new ModelExplorer(
|
||||||
|
metadataProvider,
|
||||||
|
container: htmlHelper.ViewData.ModelExplorer,
|
||||||
|
metadata: itemMetadata,
|
||||||
|
model: item);
|
||||||
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
|
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
|
||||||
|
|
||||||
var templateBuilder = new TemplateBuilder(
|
var templateBuilder = new TemplateBuilder(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNet.Mvc.Extensions;
|
using Microsoft.AspNet.Mvc.Extensions;
|
||||||
|
|
@ -67,39 +68,42 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
"Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
|
"Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeInCollection = typeof(string);
|
var elementMetadata = htmlHelper.ViewData.ModelMetadata.ElementMetadata;
|
||||||
var genericEnumerableType = ClosedGenericMatcher.ExtractGenericInterface(
|
Debug.Assert(elementMetadata != null);
|
||||||
collection.GetType(),
|
var typeInCollectionIsNullableValueType = elementMetadata.IsNullableValueType;
|
||||||
typeof(IEnumerable<>));
|
|
||||||
if (genericEnumerableType != null)
|
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
|
||||||
|
var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();
|
||||||
|
|
||||||
|
// Use typeof(string) instead of typeof(object) for IEnumerable collections. Neither type is Nullable<T>.
|
||||||
|
if (elementMetadata.ModelType == typeof(object))
|
||||||
{
|
{
|
||||||
typeInCollection = genericEnumerableType.GenericTypeArguments[0];
|
elementMetadata = metadataProvider.GetMetadataForType(typeof(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeInCollectionIsNullableValueType = TypeHelper.IsNullableValueType(typeInCollection);
|
|
||||||
var oldPrefix = viewData.TemplateInfo.HtmlFieldPrefix;
|
var oldPrefix = viewData.TemplateInfo.HtmlFieldPrefix;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
viewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
viewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
|
||||||
|
|
||||||
var fieldNameBase = oldPrefix;
|
var fieldNameBase = oldPrefix;
|
||||||
var result = new StringBuilder();
|
var result = new StringBuilder();
|
||||||
|
|
||||||
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
|
|
||||||
var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();
|
|
||||||
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var item in collection)
|
foreach (var item in collection)
|
||||||
{
|
{
|
||||||
var itemType = typeInCollection;
|
var itemMetadata = elementMetadata;
|
||||||
if (item != null && !typeInCollectionIsNullableValueType)
|
if (item != null && !typeInCollectionIsNullableValueType)
|
||||||
{
|
{
|
||||||
itemType = item.GetType();
|
itemMetadata = metadataProvider.GetMetadataForType(item.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelExplorer = metadataProvider.GetModelExplorerForType(itemType, item);
|
var modelExplorer = new ModelExplorer(
|
||||||
|
metadataProvider,
|
||||||
|
container: htmlHelper.ViewData.ModelExplorer,
|
||||||
|
metadata: itemMetadata,
|
||||||
|
model: item);
|
||||||
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
|
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
|
||||||
|
|
||||||
var templateBuilder = new TemplateBuilder(
|
var templateBuilder = new TemplateBuilder(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DefaultHtmlGenerator"/> class.
|
/// Initializes a new instance of the <see cref="DefaultHtmlGenerator"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="antiForgery">The <see cref="AntiForgery"/> instance which is used to generate anti-forgery
|
/// <param name="antiForgery">The <see cref="AntiForgery"/> instance which is used to generate anti-forgery
|
||||||
/// tokens.</param>
|
/// tokens.</param>
|
||||||
/// <param name="optionsAccessor">The accessor for <see cref="MvcOptions"/>.</param>
|
/// <param name="optionsAccessor">The accessor for <see cref="MvcOptions"/>.</param>
|
||||||
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||||
|
|
@ -796,11 +796,19 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
|
||||||
modelExplorer = modelExplorer ??
|
modelExplorer = modelExplorer ??
|
||||||
ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
|
ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
|
||||||
|
var metadata = modelExplorer.Metadata;
|
||||||
|
if (allowMultiple && metadata.IsCollectionType)
|
||||||
|
{
|
||||||
|
metadata = metadata.ElementMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
var enumNames = modelExplorer.Metadata.EnumNamesAndValues;
|
var enumNames = metadata.EnumNamesAndValues;
|
||||||
var isTargetEnum = modelExplorer.Metadata.IsEnum;
|
var isTargetEnum = metadata.IsEnum;
|
||||||
var innerType =
|
|
||||||
Nullable.GetUnderlyingType(modelExplorer.Metadata.ModelType) ?? modelExplorer.Metadata.ModelType;
|
// Logic below assumes isTargetEnum and enumNames are consistent. Confirm that expectation is met.
|
||||||
|
Debug.Assert(isTargetEnum ^ enumNames == null);
|
||||||
|
|
||||||
|
var innerType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType;
|
||||||
|
|
||||||
// Convert raw value collection to strings.
|
// Convert raw value collection to strings.
|
||||||
var currentValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var currentValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
@ -841,13 +849,18 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
var integerString = enumValue.ToString("d");
|
var integerString = enumValue.ToString("d");
|
||||||
currentValues.Add(integerString);
|
currentValues.Add(integerString);
|
||||||
|
|
||||||
// Add all simple names for this value.
|
// isTargetEnum may be false when raw value has a different type than the target e.g. ViewData
|
||||||
var matchingNames = enumNames
|
// contains enum values and property has type int or string.
|
||||||
.Where(kvp => string.Equals(integerString, kvp.Value, StringComparison.Ordinal))
|
if (isTargetEnum)
|
||||||
.Select(kvp => kvp.Key);
|
|
||||||
foreach (var name in matchingNames)
|
|
||||||
{
|
{
|
||||||
currentValues.Add(name);
|
// Add all simple names for this value.
|
||||||
|
var matchingNames = enumNames
|
||||||
|
.Where(kvp => string.Equals(integerString, kvp.Value, StringComparison.Ordinal))
|
||||||
|
.Select(kvp => kvp.Key);
|
||||||
|
foreach (var name in matchingNames)
|
||||||
|
{
|
||||||
|
currentValues.Add(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ModelMetadata ElementMetadata
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues
|
public override IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
// 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;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if !DNXCORE50
|
#if !DNXCORE50
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -52,6 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
Assert.Null(metadata.DisplayFormatString);
|
Assert.Null(metadata.DisplayFormatString);
|
||||||
Assert.Null(metadata.DisplayName);
|
Assert.Null(metadata.DisplayName);
|
||||||
Assert.Null(metadata.EditFormatString);
|
Assert.Null(metadata.EditFormatString);
|
||||||
|
Assert.Null(metadata.ElementMetadata);
|
||||||
Assert.Null(metadata.EnumDisplayNamesAndValues);
|
Assert.Null(metadata.EnumDisplayNamesAndValues);
|
||||||
Assert.Null(metadata.EnumNamesAndValues);
|
Assert.Null(metadata.EnumNamesAndValues);
|
||||||
Assert.Null(metadata.NullDisplayText);
|
Assert.Null(metadata.NullDisplayText);
|
||||||
|
|
@ -106,6 +109,69 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
Assert.Equal(typeof(Exception), metadata.ContainerType);
|
Assert.Equal(typeof(Exception), metadata.ContainerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(object))]
|
||||||
|
[InlineData(typeof(int))]
|
||||||
|
[InlineData(typeof(NonCollectionType))]
|
||||||
|
[InlineData(typeof(string))]
|
||||||
|
public void ElementMetadata_ReturnsNull_ForNonCollections(Type modelType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||||
|
|
||||||
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var elementMetadata = metadata.ElementMetadata;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(elementMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(int[]), typeof(int))]
|
||||||
|
[InlineData(typeof(List<string>), typeof(string))]
|
||||||
|
[InlineData(typeof(DerivedList), typeof(int))]
|
||||||
|
[InlineData(typeof(IEnumerable), typeof(object))]
|
||||||
|
[InlineData(typeof(IEnumerable<string>), typeof(string))]
|
||||||
|
[InlineData(typeof(Collection<int>), typeof(int))]
|
||||||
|
[InlineData(typeof(Dictionary<object, object>), typeof(KeyValuePair<object, object>))]
|
||||||
|
[InlineData(typeof(DerivedDictionary), typeof(KeyValuePair<string, int>))]
|
||||||
|
public void ElementMetadata_ReturnsExpectedMetadata(Type modelType, Type elementType)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new EmptyModelMetadataProvider();
|
||||||
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
|
var key = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
|
||||||
|
|
||||||
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var elementMetadata = metadata.ElementMetadata;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(elementMetadata);
|
||||||
|
Assert.Equal(elementType, elementMetadata.ModelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonCollectionType
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedList : List<int>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedDictionary : Dictionary<string, int>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(typeof(string))]
|
[InlineData(typeof(string))]
|
||||||
[InlineData(typeof(IDisposable))]
|
[InlineData(typeof(IDisposable))]
|
||||||
|
|
@ -160,7 +226,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var expectedProperties = new DefaultModelMetadata[]
|
var expectedProperties = new DefaultModelMetadata[]
|
||||||
{
|
{
|
||||||
new DefaultModelMetadata(
|
new DefaultModelMetadata(
|
||||||
provider.Object,
|
provider.Object,
|
||||||
detailsProvider,
|
detailsProvider,
|
||||||
new DefaultMetadataDetails(
|
new DefaultMetadataDetails(
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)),
|
||||||
|
|
|
||||||
|
|
@ -874,7 +874,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
Assert.Equal(expectedHtml, html.ToString());
|
Assert.Equal(expectedHtml, html.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "#2664, throws ArgumentNullException")]
|
[Fact]
|
||||||
public void ListBoxNotInTemplate_GetsViewDataEntry_IfModelStateEmpty()
|
public void ListBoxNotInTemplate_GetsViewDataEntry_IfModelStateEmpty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -899,7 +899,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
Assert.Equal(expectedHtml, html.ToString());
|
Assert.Equal(expectedHtml, html.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "#2664, throws ArgumentNullException")]
|
[Fact(Skip = "#1487, incorrectly matches Property1 entry (without prefix) in ViewData.")]
|
||||||
public void ListBoxInTemplate_GetsViewDataEntry_IfModelStateEmpty()
|
public void ListBoxInTemplate_GetsViewDataEntry_IfModelStateEmpty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -927,7 +927,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
Assert.Equal(expectedHtml, html.ToString());
|
Assert.Equal(expectedHtml, html.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "#2664, throws ArgumentNullException")]
|
[Fact(Skip = "#1487, incorrectly matches Property1 entry (without prefix) in ViewData.")]
|
||||||
public void ListBoxInTemplate_GetsPropertyOfViewDataEntry_IfModelStateEmptyAndNoViewDataEntryWithPrefix()
|
public void ListBoxInTemplate_GetsPropertyOfViewDataEntry_IfModelStateEmptyAndNoViewDataEntryWithPrefix()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -954,7 +954,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
Assert.Equal(expectedHtml, html.ToString());
|
Assert.Equal(expectedHtml, html.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "#2664, throws ArgumentNullException")]
|
[Fact]
|
||||||
public void ListBoxNotInTemplate_GetsPropertyOfModel_IfModelStateAndViewDataEmpty()
|
public void ListBoxNotInTemplate_GetsPropertyOfModel_IfModelStateAndViewDataEmpty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -970,7 +970,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
Assert.Equal(expectedHtml, html.ToString());
|
Assert.Equal(expectedHtml, html.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "#2664, throws ArgumentNullException")]
|
[Fact]
|
||||||
public void ListBoxInTemplate_GetsPropertyOfModel_IfModelStateAndViewDataEmpty()
|
public void ListBoxInTemplate_GetsPropertyOfModel_IfModelStateAndViewDataEmpty()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue