aspnetcore/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs

626 lines
25 KiB
C#

// 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.Linq;
#if !DNXCORE50
using Moq;
#endif
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
{
public class DefaultModelMetadataTest
{
[Fact]
public void DefaultValues()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new DefaultCompositeMetadataDetailsProvider(
Enumerable.Empty<IMetadataDetailsProvider>());
var key = ModelMetadataIdentity.ForType(typeof(string));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
// Act
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Assert
Assert.NotNull(metadata.AdditionalValues);
Assert.Empty(metadata.AdditionalValues);
Assert.Equal(typeof(string), metadata.ModelType);
Assert.True(metadata.ConvertEmptyStringToNull);
Assert.False(metadata.HasNonDefaultEditFormat);
Assert.False(metadata.HideSurroundingHtml);
Assert.True(metadata.HtmlEncode);
Assert.True(metadata.IsBindingAllowed);
Assert.False(metadata.IsBindingRequired);
Assert.False(metadata.IsCollectionType);
Assert.False(metadata.IsComplexType);
Assert.False(metadata.IsEnumerableType);
Assert.False(metadata.IsEnum);
Assert.False(metadata.IsFlagsEnum);
Assert.False(metadata.IsNullableValueType);
Assert.False(metadata.IsReadOnly);
Assert.False(metadata.IsRequired); // Defaults to false for reference types
Assert.True(metadata.ShowForDisplay);
Assert.True(metadata.ShowForEdit);
Assert.Null(metadata.DataTypeName);
Assert.Null(metadata.Description);
Assert.Null(metadata.DisplayFormatString);
Assert.Null(metadata.DisplayName);
Assert.Null(metadata.EditFormatString);
Assert.Null(metadata.ElementMetadata);
Assert.Null(metadata.EnumGroupedDisplayNamesAndValues);
Assert.Null(metadata.EnumNamesAndValues);
Assert.Null(metadata.NullDisplayText);
Assert.Null(metadata.TemplateHint);
Assert.Null(metadata.SimpleDisplayProperty);
Assert.Equal(10000, ModelMetadata.DefaultOrder);
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
Assert.Null(metadata.BinderModelName);
Assert.Null(metadata.BinderType);
Assert.Null(metadata.BindingSource);
Assert.Null(metadata.PropertyBindingPredicateProvider);
}
[Fact]
public void CreateMetadataForType()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new DefaultCompositeMetadataDetailsProvider(
Enumerable.Empty<IMetadataDetailsProvider>());
var key = ModelMetadataIdentity.ForType(typeof(Exception));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
// Act
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Assert
Assert.Equal(typeof(Exception), metadata.ModelType);
Assert.Null(metadata.PropertyName);
Assert.Null(metadata.ContainerType);
}
[Fact]
public void CreateMetadataForProperty()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForProperty(typeof(string), "Message", typeof(Exception));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], new object[0]));
// Act
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Assert
Assert.Equal(typeof(string), metadata.ModelType);
Assert.Equal("Message", metadata.PropertyName);
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]
[InlineData(typeof(string))]
[InlineData(typeof(int))]
public void IsBindingAllowed_ReturnsTrue_ForTypes(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]));
cache.BindingMetadata = new BindingMetadata()
{
IsBindingAllowed = false, // Will be ignored.
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var isBindingAllowed = metadata.IsBindingAllowed;
// Assert
Assert.True(isBindingAllowed);
}
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(int))]
public void IsBindingRequired_ReturnsFalse_ForTypes(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]));
cache.BindingMetadata = new BindingMetadata()
{
IsBindingRequired = true, // Will be ignored.
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var isBindingRequired = metadata.IsBindingRequired;
// Assert
Assert.False(isBindingRequired);
}
[Theory]
[InlineData(typeof(string))]
[InlineData(typeof(IDisposable))]
[InlineData(typeof(Nullable<int>))]
public void IsRequired_ReturnsFalse_ForNullableTypes(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 isRequired = metadata.IsRequired;
// Assert
Assert.False(isRequired);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(DayOfWeek))]
public void IsRequired_ReturnsTrue_ForNonNullableTypes(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 isRequired = metadata.IsRequired;
// Assert
Assert.True(isRequired);
}
#if !DNXCORE50
[Fact]
public void PropertiesProperty_CallsProvider()
{
// Arrange
var provider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var expectedProperties = new DefaultModelMetadata[]
{
new DefaultModelMetadata(
provider.Object,
detailsProvider,
new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0]))),
new DefaultModelMetadata(
provider.Object,
detailsProvider,
new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), "Prop2", typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0]))),
};
provider
.Setup(p => p.GetMetadataForProperties(typeof(string)))
.Returns(expectedProperties);
var key = ModelMetadataIdentity.ForType(typeof(string));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
// Act
var properties = metadata.Properties;
// Assert
Assert.Equal(expectedProperties.Length, properties.Count);
for (var i = 0; i < expectedProperties.Length; i++)
{
Assert.Same(expectedProperties[i], properties[i]);
}
}
// Input (original) property names and expected (ordered) property names.
public static TheoryData<IEnumerable<string>, IEnumerable<string>> PropertyNamesTheoryData
{
get
{
// ModelMetadata does not reorder properties the provider returns without an Order override.
return new TheoryData<IEnumerable<string>, IEnumerable<string>>
{
{
new List<string> { "Property1", "Property2", "Property3", "Property4", },
new List<string> { "Property1", "Property2", "Property3", "Property4", }
},
{
new List<string> { "Property4", "Property3", "Property2", "Property1", },
new List<string> { "Property4", "Property3", "Property2", "Property1", }
},
{
new List<string> { "Delta", "Bravo", "Charlie", "Alpha", },
new List<string> { "Delta", "Bravo", "Charlie", "Alpha", }
},
{
new List<string> { "John", "Jonathan", "Jon", "Joan", },
new List<string> { "John", "Jonathan", "Jon", "Joan", }
},
};
}
}
[Theory]
[MemberData(nameof(PropertyNamesTheoryData))]
public void PropertiesProperty_WithDefaultOrder_OrdersPropertyNamesAsProvided(
IEnumerable<string> originalNames,
IEnumerable<string> expectedNames)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var expectedProperties = new List<DefaultModelMetadata>();
foreach (var originalName in originalNames)
{
expectedProperties.Add(new DefaultModelMetadata(
provider.Object,
detailsProvider,
new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0]))));
}
provider
.Setup(p => p.GetMetadataForProperties(typeof(string)))
.Returns(expectedProperties);
var key = ModelMetadataIdentity.ForType(typeof(string));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
// Act
var properties = metadata.Properties;
// Assert
Assert.Equal(expectedNames.Count(), properties.Count);
Assert.Equal(expectedNames.ToArray(), properties.Select(p => p.PropertyName).ToArray());
}
// Input (original) property names, Order values, and expected (ordered) property names.
public static TheoryData<IEnumerable<KeyValuePair<string, int>>, IEnumerable<string>>
PropertyNamesAndOrdersTheoryData
{
get
{
return new TheoryData<IEnumerable<KeyValuePair<string, int>>, IEnumerable<string>>
{
{
new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("Property1", 23),
new KeyValuePair<string, int>("Property2", 23),
new KeyValuePair<string, int>("Property3", 23),
new KeyValuePair<string, int>("Property4", 23),
},
new List<string> { "Property1", "Property2", "Property3", "Property4", }
},
// Same order if already ordered using Order.
{
new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("Property4", 23),
new KeyValuePair<string, int>("Property3", 24),
new KeyValuePair<string, int>("Property2", 25),
new KeyValuePair<string, int>("Property1", 26),
},
new List<string> { "Property4", "Property3", "Property2", "Property1", }
},
// Rest of the orderings get updated within ModelMetadata.
{
new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("Property1", 26),
new KeyValuePair<string, int>("Property2", 25),
new KeyValuePair<string, int>("Property3", 24),
new KeyValuePair<string, int>("Property4", 23),
},
new List<string> { "Property4", "Property3", "Property2", "Property1", }
},
{
new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("Alpha", 26),
new KeyValuePair<string, int>("Bravo", 24),
new KeyValuePair<string, int>("Charlie", 23),
new KeyValuePair<string, int>("Delta", 25),
},
new List<string> { "Charlie", "Bravo", "Delta", "Alpha", }
},
// Jonathan and Jon will not be reordered.
{
new List<KeyValuePair<string, int>>
{
new KeyValuePair<string, int>("Joan", 1),
new KeyValuePair<string, int>("Jonathan", 0),
new KeyValuePair<string, int>("Jon", 0),
new KeyValuePair<string, int>("John", -1),
},
new List<string> { "John", "Jonathan", "Jon", "Joan", }
},
};
}
}
[Theory]
[MemberData(nameof(PropertyNamesAndOrdersTheoryData))]
public void PropertiesProperty_OrdersPropertyNamesUsingOrder_ThenAsProvided(
IEnumerable<KeyValuePair<string, int>> originalNamesAndOrders,
IEnumerable<string> expectedNames)
{
// Arrange
var provider = new Mock<IModelMetadataProvider>(MockBehavior.Strict);
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var expectedProperties = new List<DefaultModelMetadata>();
foreach (var kvp in originalNamesAndOrders)
{
var propertyCache = new DefaultMetadataDetails(
ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)),
attributes: new ModelAttributes(new object[0], new object[0]));
propertyCache.DisplayMetadata = new DisplayMetadata();
propertyCache.DisplayMetadata.Order = kvp.Value;
expectedProperties.Add(new DefaultModelMetadata(
provider.Object,
detailsProvider,
propertyCache));
}
provider
.Setup(p => p.GetMetadataForProperties(typeof(string)))
.Returns(expectedProperties);
var key = ModelMetadataIdentity.ForType(typeof(string));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
// Act
var properties = metadata.Properties;
// Assert
Assert.Equal(expectedNames.Count(), properties.Count);
Assert.Equal(expectedNames.ToArray(), properties.Select(p => p.PropertyName).ToArray());
}
#endif
[Fact]
public void PropertiesSetOnce()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForType(typeof(string));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var firstPropertiesEvaluation = metadata.Properties;
var secondPropertiesEvaluation = metadata.Properties;
// Assert
// Same IEnumerable<ModelMetadata> object.
Assert.Same(firstPropertiesEvaluation, secondPropertiesEvaluation);
}
[Fact]
public void PropertiesEnumerationEvaluatedOnce()
{
// Arrange
var provider = new EmptyModelMetadataProvider();
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var key = ModelMetadataIdentity.ForType(typeof(string));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var firstPropertiesEvaluation = metadata.Properties.ToList();
var secondPropertiesEvaluation = metadata.Properties.ToList();
// Assert
// Identical ModelMetadata objects every time we run through the Properties collection.
Assert.Equal(firstPropertiesEvaluation, secondPropertiesEvaluation);
}
[Fact]
public void IsReadOnly_ReturnsFalse_ForType()
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(int[]));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
cache.BindingMetadata = new BindingMetadata()
{
IsReadOnly = true, // Will be ignored.
};
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var isReadOnly = metadata.IsReadOnly;
// Assert
Assert.False(isReadOnly);
}
[Fact]
public void IsReadOnly_ReturnsTrue_ForPrivateSetProperty()
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var isReadOnly = metadata.Properties["PublicGetPrivateSetProperty"].IsReadOnly;
// Assert
Assert.True(isReadOnly);
}
[Fact]
public void IsReadOnly_ReturnsTrue_ForProtectedSetProperty()
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var isReadOnly = metadata.Properties["PublicGetProtectedSetProperty"].IsReadOnly;
// Assert
Assert.True(isReadOnly);
}
[Fact]
public void IsReadOnly_ReturnsFalse_ForPublicSetProperty()
{
// Arrange
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
var provider = new DefaultModelMetadataProvider(detailsProvider);
var key = ModelMetadataIdentity.ForType(typeof(TypeWithProperties));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
// Act
var isReadOnly = metadata.Properties["PublicGetPublicSetProperty"].IsReadOnly;
// Assert
Assert.False(isReadOnly);
}
private void ActionMethod(string input)
{
}
private class TypeWithProperties
{
public string PublicGetPrivateSetProperty { get; private set; }
public int PublicGetProtectedSetProperty { get; protected set; }
public int PublicGetPublicSetProperty { get; set; }
}
}
}