Add `IsReferenceOrNullableType` and `UnderlyingOrModelType` to `ModelMetadata`

- #2992
- use new properties to replace common helper methods
- still a few `Nullable.GetUnderlyingType()` calls
  - creating `ModelMetadata` or sites lacking `ModelMetadata` access e.g. `ModelBindingHelper.ConvertTo()`
This commit is contained in:
Doug Bunting 2015-08-22 14:02:24 -07:00
parent 0beb39ec1c
commit bf7e0f141e
11 changed files with 124 additions and 96 deletions

View File

@ -5,9 +5,7 @@ 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;
@ -139,8 +137,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public abstract ModelMetadata ElementMetadata { get; } 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
/// <c>Nullable.GetUnderlyingType(ModelType)</c>. /// <see cref="UnderlyingOrModelType"/>.
/// </summary> /// </summary>
/// <value> /// <value>
/// An <see cref="IEnumerable{KeyValuePair{string, string}}"/> of mappings between <see cref="Enum"/> field names /// An <see cref="IEnumerable{KeyValuePair{string, string}}"/> of mappings between <see cref="Enum"/> field names
@ -149,8 +147,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public abstract IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues { get; } public abstract IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues { get; }
/// <summary> /// <summary>
/// Gets the names and values of all <see cref="Enum"/> values in <see cref="ModelType"/> or /// Gets the names and values of all <see cref="Enum"/> values in <see cref="UnderlyingOrModelType"/>.
/// <c>Nullable.GetUnderlyingType(ModelType)</c>.
/// </summary> /// </summary>
/// <value> /// <value>
/// An <see cref="IReadOnlyDictionary{string, string}"/> of mappings between <see cref="Enum"/> field names /// An <see cref="IReadOnlyDictionary{string, string}"/> of mappings between <see cref="Enum"/> field names
@ -204,23 +201,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public abstract bool IsBindingRequired { get; } public abstract bool IsBindingRequired { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="ModelType"/> or <c>Nullable.GetUnderlyingType(ModelType)</c> is /// Gets a value indicating whether <see cref="UnderlyingOrModelType"/> is for an <see cref="Enum"/>.
/// for an <see cref="Enum"/>.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if <c>type.IsEnum</c> (<c>type.GetTypeInfo().IsEnum</c> for DNX Core 5.0) is <c>true</c> for /// <c>true</c> if <c>type.IsEnum</c> (<c>type.GetTypeInfo().IsEnum</c> for DNX Core 5.0) is <c>true</c> for
/// <see cref="ModelType"/> or <c>Nullable.GetUnderlyingType(ModelType)</c>; <c>false</c> otherwise. /// <see cref="UnderlyingOrModelType"/>; <c>false</c> otherwise.
/// </value> /// </value>
public abstract bool IsEnum { get; } public abstract bool IsEnum { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="ModelType"/> or <c>Nullable.GetUnderlyingType(ModelType)</c> is /// Gets a value indicating whether <see cref="UnderlyingOrModelType"/> is for an <see cref="Enum"/> with an
/// for an <see cref="Enum"/> with an associated <see cref="FlagsAttribute"/>. /// associated <see cref="FlagsAttribute"/>.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if <see cref="IsEnum"/> is <c>true</c> and <see cref="ModelType"/> or /// <c>true</c> if <see cref="IsEnum"/> is <c>true</c> and <see cref="UnderlyingOrModelType"/> has an
/// <c>Nullable.GetUnderlyingType(ModelType)</c> has an associated <see cref="FlagsAttribute"/>; <c>false</c> /// associated <see cref="FlagsAttribute"/>; <c>false</c> otherwise.
/// otherwise.
/// </value> /// </value>
public abstract bool IsFlagsEnum { get; } public abstract bool IsFlagsEnum { get; }
@ -346,6 +341,32 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
} }
/// <summary>
/// Gets a value indicating whether or not <see cref="ModelType"/> allows <c>null</c> values.
/// </summary>
public bool IsReferenceOrNullableType
{
get
{
return !ModelType.GetTypeInfo().IsValueType || IsNullableValueType;
}
}
/// <summary>
/// Gets the underlying type argument if <see cref="ModelType"/> inherits from <see cref="Nullable{T}"/>.
/// Otherwise gets <see cref="ModelType"/>.
/// </summary>
/// <remarks>
/// Identical to <see cref="ModelType"/> unless <see cref="IsNullableValueType"/> is <c>true</c>.
/// </remarks>
public Type UnderlyingOrModelType
{
get
{
return Nullable.GetUnderlyingType(ModelType) ?? ModelType;
}
}
/// <summary> /// <summary>
/// Gets a property getter delegate to get the property value from a model object. /// Gets a property getter delegate to get the property value from a model object.
/// </summary> /// </summary>

View File

@ -127,8 +127,11 @@ namespace Microsoft.AspNet.Mvc
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. Do not set the property if the type is a non-nullable type. // Handle settable property.
if (source != null || AllowsNullValue(propertyType)) 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)
{ {
propertyHelper.SetValue(controller, source); propertyHelper.SetValue(controller, source);
} }
@ -218,10 +221,5 @@ namespace Microsoft.AspNet.Mvc
ValueProvider = bindingContext.ValueProvider, ValueProvider = bindingContext.ValueProvider,
}; };
} }
private static bool AllowsNullValue([NotNull] Type type)
{
return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null;
}
} }
} }

View File

@ -7,7 +7,9 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
#if DNXCORE50
using System.Reflection; using System.Reflection;
#endif
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
@ -407,8 +409,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
} }
else else
{ {
// Default to IsRequired = true for value types. // Default to IsRequired = true for non-Nullable<T> value types.
_isRequired = !AllowsNullValue(ModelType); _isRequired = !IsReferenceOrNullableType;
} }
} }
@ -526,10 +528,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
return _details.PropertySetter; return _details.PropertySetter;
} }
} }
private static bool AllowsNullValue([NotNull] Type type)
{
return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null;
}
} }
} }

View File

@ -58,14 +58,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// <summary> /// <summary>
/// Gets the ordered display names and values of all <see cref="System.Enum"/> values in /// Gets the ordered display names and values of all <see cref="System.Enum"/> values in
/// <see cref="ModelMetadata.ModelType"/> or <c>Nullable.GetUnderlyingType(ModelType)</c>. See /// <see cref="ModelMetadata.UnderlyingOrModelType"/>. See
/// <see cref="ModelMetadata.EnumDisplayNamesAndValues"/>. /// <see cref="ModelMetadata.EnumDisplayNamesAndValues"/>.
/// </summary> /// </summary>
public IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues { get; set; } public IEnumerable<KeyValuePair<string, string>> EnumDisplayNamesAndValues { get; set; }
/// <summary> /// <summary>
/// Gets the names and values of all <see cref="System.Enum"/> values in <see cref="ModelMetadata.ModelType"/> /// Gets the names and values of all <see cref="System.Enum"/> values in
/// or <c>Nullable.GetUnderlyingType(ModelType)</c>. See <see cref="ModelMetadata.EnumNamesAndValues"/>. /// <see cref="ModelMetadata.UnderlyingOrModelType"/>. See <see cref="ModelMetadata.EnumNamesAndValues"/>.
/// </summary> /// </summary>
// This could be implemented in DefaultModelMetadata. But value should be cached. // This could be implemented in DefaultModelMetadata. But value should be cached.
public IReadOnlyDictionary<string, string> EnumNamesAndValues { get; set; } public IReadOnlyDictionary<string, string> EnumNamesAndValues { get; set; }
@ -89,17 +89,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
public bool HtmlEncode { get; set; } = true; public bool HtmlEncode { get; set; } = true;
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="ModelMetadata.ModelType"/> or /// Gets a value indicating whether <see cref="ModelMetadata.UnderlyingOrModelType"/> is for an
/// <c>Nullable.GetUnderlyingType(ModelType)</c> is for an <see cref="System.Enum"/>. See /// <see cref="System.Enum"/>. See <see cref="ModelMetadata.IsEnum"/>.
/// <see cref="ModelMetadata.IsEnum"/>.
/// </summary> /// </summary>
// This could be implemented in DefaultModelMetadata. But value is needed in the details provider. // This could be implemented in DefaultModelMetadata. But value is needed in the details provider.
public bool IsEnum { get; set; } public bool IsEnum { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="ModelMetadata.ModelType"/> or /// Gets a value indicating whether <see cref="ModelMetadata.UnderlyingOrModelType"/> is for an
/// <c>Nullable.GetUnderlyingType(ModelType)</c> is for an <see cref="System.Enum"/> with an associated /// <see cref="System.Enum"/> with an associated <see cref="System.FlagsAttribute"/>. See
/// <see cref="System.FlagsAttribute"/>. See <see cref="ModelMetadata.IsFlagsEnum"/>. /// <see cref="ModelMetadata.IsFlagsEnum"/>.
/// </summary> /// </summary>
// This could be implemented in DefaultModelMetadata. But value is needed in the details provider. // This could be implemented in DefaultModelMetadata. But value is needed in the details provider.
public bool IsFlagsEnum { get; set; } public bool IsFlagsEnum { get; set; }

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
@ -51,7 +50,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// When converting newModel a null value may indicate a failed conversion for an otherwise required // When converting newModel a null value may indicate a failed conversion for an otherwise required
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the // model (can't set a ValueType to null). This detects if a null model value is acceptable given the
// current bindingContext. If not, an error is logged. // current bindingContext. If not, an error is logged.
if (model == null && !AllowsNullValue(bindingContext.ModelType)) if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
{ {
bindingContext.ModelState.TryAddModelError( bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName, bindingContext.ModelName,
@ -83,10 +82,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
key: bindingContext.ModelName, key: bindingContext.ModelName,
isModelSet: false); isModelSet: false);
} }
private static bool AllowsNullValue(Type type)
{
return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null;
}
} }
} }

View File

@ -1,9 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{ {
/// <summary> /// <summary>
@ -15,8 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
/// <inheritdoc /> /// <inheritdoc />
public void GetValidators(ClientValidatorProviderContext context) public void GetValidators(ClientValidatorProviderContext context)
{ {
var type = context.ModelMetadata.ModelType; var typeToValidate = context.ModelMetadata.UnderlyingOrModelType;
var typeToValidate = Nullable.GetUnderlyingType(type) ?? type;
// Check only the numeric types for which we set type='text'. // Check only the numeric types for which we set type='text'.
if (typeToValidate == typeof(float) || if (typeToValidate == typeof(float) ||

View File

@ -386,11 +386,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var fieldType = modelExplorer.ModelType; var fieldType = modelExplorer.ModelType;
if (typeof(bool?) != fieldType) if (typeof(bool?) != fieldType)
{ {
var underlyingType = Nullable.GetUnderlyingType(fieldType); fieldType = modelExplorer.Metadata.UnderlyingOrModelType;
if (underlyingType != null)
{
fieldType = underlyingType;
}
} }
foreach (string typeName in TemplateRenderer.GetTypeNames(modelExplorer.Metadata, fieldType)) foreach (string typeName in TemplateRenderer.GetTypeNames(modelExplorer.Metadata, fieldType))

View File

@ -8,7 +8,6 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using Microsoft.AspNet.Antiforgery; using Microsoft.AspNet.Antiforgery;
using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
@ -838,7 +837,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Logic below assumes isTargetEnum and enumNames are consistent. Confirm that expectation is met. // Logic below assumes isTargetEnum and enumNames are consistent. Confirm that expectation is met.
Debug.Assert(isTargetEnum ^ enumNames == null); Debug.Assert(isTargetEnum ^ enumNames == null);
var innerType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType; var innerType = metadata.UnderlyingOrModelType;
// 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);

View File

@ -146,10 +146,8 @@ namespace Microsoft.AspNet.Mvc.Rendering.Internal
// We don't want to search for Nullable<T>, we want to search for T (which should handle both T and // We don't want to search for Nullable<T>, we want to search for T (which should handle both T and
// Nullable<T>). // Nullable<T>).
var modelType = _viewData.ModelExplorer.ModelType; var fieldType = metadata.UnderlyingOrModelType;
var fieldType = Nullable.GetUnderlyingType(modelType) ?? modelType; foreach (var typeName in GetTypeNames(metadata, fieldType))
foreach (var typeName in GetTypeNames(_viewData.ModelExplorer.Metadata, fieldType))
{ {
yield return typeName; yield return typeName;
} }

View File

@ -5,7 +5,9 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
#if DNXCORE50
using System.Reflection; using System.Reflection;
#endif
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering.Expressions; using Microsoft.AspNet.Mvc.Rendering.Expressions;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
@ -186,8 +188,7 @@ namespace Microsoft.AspNet.Mvc
{ {
// This is the core constructor called when Model is known. // This is the core constructor called when Model is known.
var modelType = GetModelType(model); var modelType = GetModelType(model);
var metadataModelType = var metadataModelType = source.ModelMetadata.UnderlyingOrModelType;
Nullable.GetUnderlyingType(source.ModelMetadata.ModelType) ?? source.ModelMetadata.ModelType;
if (modelType == metadataModelType && model == source.ModelExplorer.Model) if (modelType == metadataModelType && model == source.ModelExplorer.Model)
{ {
// Preserve any customizations made to source.ModelExplorer.ModelMetadata if the Type // Preserve any customizations made to source.ModelExplorer.ModelMetadata if the Type
@ -260,7 +261,7 @@ namespace Microsoft.AspNet.Mvc
public TemplateInfo TemplateInfo { get; } public TemplateInfo TemplateInfo { get; }
#region IDictionary properties #region IDictionary properties
// Do not just pass through to _data: Indexer should not throw a KeyNotFoundException. // Do not just pass through to _data: Indexer should not throw a KeyNotFoundException.
public object this[string index] public object this[string index]
{ {
@ -295,7 +296,7 @@ namespace Microsoft.AspNet.Mvc
{ {
get { return _data.Values; } get { return _data.Values; }
} }
#endregion #endregion
// for unit testing // for unit testing
internal IDictionary<string, object> Data internal IDictionary<string, object> Data
@ -383,13 +384,13 @@ namespace Microsoft.AspNet.Mvc
EnsureCompatible(value); EnsureCompatible(value);
// Reset or override ModelMetadata based on runtime value type. Fall back to declared type if value is // Reset or override ModelMetadata based on runtime value type. Fall back to declared type if value is
// null. When called from the Model setter, ModelMetadata will (temporarily) be null. When called from // null. When called from a constructor, current ModelExplorer may already be set to preserve
// a constructor, current ModelMetadata may already be set to preserve customizations made in parent scope. // customizations made in parent scope. But ModelExplorer is never null after instance is initialized.
var modelType = GetModelType(value); var modelType = GetModelType(value);
Type metadataModelType = null; Type metadataModelType = null;
if (ModelExplorer != null) if (ModelExplorer != null)
{ {
metadataModelType = Nullable.GetUnderlyingType(ModelMetadata.ModelType) ?? ModelMetadata.ModelType; metadataModelType = ModelMetadata.UnderlyingOrModelType;
} }
if (metadataModelType != modelType) if (metadataModelType != modelType)
@ -413,7 +414,7 @@ namespace Microsoft.AspNet.Mvc
{ {
// IsCompatibleObject verifies if the value is either an instance of _declaredModelType or (if value is // IsCompatibleObject verifies if the value is either an instance of _declaredModelType or (if value is
// null) that _declaredModelType is a nullable type. // null) that _declaredModelType is a nullable type.
var castWillSucceed = IsCompatibleWith(_declaredModelType, value); var castWillSucceed = IsCompatibleWithDeclaredType(value);
if (!castWillSucceed) if (!castWillSucceed)
{ {
string message; string message;
@ -435,19 +436,20 @@ namespace Microsoft.AspNet.Mvc
return (value == null) ? _declaredModelType : value.GetType(); return (value == null) ? _declaredModelType : value.GetType();
} }
private static bool IsCompatibleWith([NotNull] Type type, object value) private bool IsCompatibleWithDeclaredType(object value)
{ {
if (value == null) if (value == null)
{ {
return !type.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(type) != null; // In this case ModelMetadata.ModelType matches _declaredModelType.
return ModelMetadata.IsReferenceOrNullableType;
} }
else else
{ {
return type.GetTypeInfo().IsAssignableFrom(value.GetType().GetTypeInfo()); return _declaredModelType.IsAssignableFrom(value.GetType());
} }
} }
#region IDictionary methods #region IDictionary methods
public void Add([NotNull] string key, object value) public void Add([NotNull] string key, object value)
{ {
_data.Add(key, value); _data.Add(key, value);
@ -502,6 +504,6 @@ namespace Microsoft.AspNet.Mvc
{ {
return _data.GetEnumerator(); return _data.GetEnumerator();
} }
#endregion #endregion
} }
} }

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[InlineData(typeof(string))] [InlineData(typeof(string))]
[InlineData(typeof(Nullable<int>))] [InlineData(typeof(Nullable<int>))]
[InlineData(typeof(int))] [InlineData(typeof(int))]
public void IsComplexTypeTestsReturnsFalseForSimpleTypes(Type type) public void IsComplexType_ReturnsFalseForSimpleTypes(Type type)
{ {
// Arrange // Arrange
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();
@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[InlineData(typeof(IDisposable))] [InlineData(typeof(IDisposable))]
[InlineData(typeof(IsComplexTypeModel))] [InlineData(typeof(IsComplexTypeModel))]
[InlineData(typeof(Nullable<IsComplexTypeModel>))] [InlineData(typeof(Nullable<IsComplexTypeModel>))]
public void IsComplexTypeTestsReturnsTrueForComplexTypes(Type type) public void IsComplexType_ReturnsTrueForComplexTypes(Type type)
{ {
// Arrange // Arrange
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();
@ -50,12 +50,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(modelMetadata.IsComplexType); Assert.True(modelMetadata.IsComplexType);
} }
// IsCollectionType
private class NonCollectionType
{
}
private class DerivedList : List<int>
{
}
[Theory] [Theory]
[InlineData(typeof(object))] [InlineData(typeof(object))]
[InlineData(typeof(int))] [InlineData(typeof(int))]
[InlineData(typeof(NonCollectionType))] [InlineData(typeof(NonCollectionType))]
[InlineData(typeof(string))] [InlineData(typeof(string))]
public void IsCollectionType_NonCollectionTypes(Type type) public void IsCollectionType_ReturnsFalseForNonCollectionTypes(Type type)
{ {
// Arrange // Arrange
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();
@ -75,7 +84,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[InlineData(typeof(IEnumerable<string>))] [InlineData(typeof(IEnumerable<string>))]
[InlineData(typeof(Collection<int>))] [InlineData(typeof(Collection<int>))]
[InlineData(typeof(Dictionary<object, object>))] [InlineData(typeof(Dictionary<object, object>))]
public void IsCollectionType_CollectionTypes(Type type) public void IsCollectionType_ReturnsTrueForCollectionTypes(Type type)
{ {
// Arrange // Arrange
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();
@ -87,14 +96,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.True(modelMetadata.IsCollectionType); Assert.True(modelMetadata.IsCollectionType);
} }
private class NonCollectionType
{
}
private class DerivedList : List<int>
{
}
// IsNullableValueType // IsNullableValueType
[Theory] [Theory]
@ -102,7 +103,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[InlineData(typeof(IDisposable), false)] [InlineData(typeof(IDisposable), false)]
[InlineData(typeof(Nullable<int>), true)] [InlineData(typeof(Nullable<int>), true)]
[InlineData(typeof(int), false)] [InlineData(typeof(int), false)]
public void IsNullableValueTypeTests(Type modelType, bool expected) [InlineData(typeof(DerivedList), false)]
[InlineData(typeof(IsComplexTypeModel), false)]
[InlineData(typeof(Nullable<IsComplexTypeModel>), true)]
public void IsNullableValueType_ReturnsExpectedValue(Type modelType, bool expected)
{ {
// Arrange // Arrange
var modelMetadata = new TestModelMetadata(modelType); var modelMetadata = new TestModelMetadata(modelType);
@ -111,18 +115,42 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Equal(expected, modelMetadata.IsNullableValueType); Assert.Equal(expected, modelMetadata.IsNullableValueType);
} }
private class Class1 // IsReferenceOrNullableType
[Theory]
[InlineData(typeof(string), true)]
[InlineData(typeof(IDisposable), true)]
[InlineData(typeof(Nullable<int>), true)]
[InlineData(typeof(int), false)]
[InlineData(typeof(DerivedList), true)]
[InlineData(typeof(IsComplexTypeModel), false)]
[InlineData(typeof(Nullable<IsComplexTypeModel>), true)]
public void IsReferenceOrNullableType_ReturnsExpectedValue(Type modelType, bool expected)
{ {
public string Prop1 { get; set; } // Arrange
public override string ToString() var modelMetadata = new TestModelMetadata(modelType);
{
return "Class1"; // Act & Assert
} Assert.Equal(expected, modelMetadata.IsReferenceOrNullableType);
} }
private class Class2 // UnderlyingOrModelType
[Theory]
[InlineData(typeof(string), typeof(string))]
[InlineData(typeof(IDisposable), typeof(IDisposable))]
[InlineData(typeof(Nullable<int>), typeof(int))]
[InlineData(typeof(int), typeof(int))]
[InlineData(typeof(DerivedList), typeof(DerivedList))]
[InlineData(typeof(IsComplexTypeModel), typeof(IsComplexTypeModel))]
[InlineData(typeof(Nullable<IsComplexTypeModel>), typeof(IsComplexTypeModel))]
public void UnderlyingOrModelType_ReturnsExpectedValue(Type modelType, Type expected)
{ {
public int Prop2 { get; set; } // Arrange
var modelMetadata = new TestModelMetadata(modelType);
// Act & Assert
Assert.Equal(expected, modelMetadata.UnderlyingOrModelType);
} }
// GetDisplayName() // GetDisplayName()
@ -143,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
[Fact] [Fact]
public void ReturnsPropertyNameWhenSetAndDisplayNameIsNull() public void GetDisplayName_ReturnsPropertyName_WhenSetAndDisplayNameIsNull()
{ {
// Arrange // Arrange
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();
@ -157,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
[Fact] [Fact]
public void ReturnsTypeNameWhenPropertyNameAndDisplayNameAreNull() public void GetDisplayName_ReturnsTypeName_WhenPropertyNameAndDisplayNameAreNull()
{ {
// Arrange // Arrange
var provider = new EmptyModelMetadataProvider(); var provider = new EmptyModelMetadataProvider();