[Perf] Fully cache model metadata
This change caches the actual model metadata instances. Some profiling showed we didn't go far enough, we were allocating a lot of ModelMetadata + ModelPropertyCollection instances.
This commit is contained in:
parent
08f0e1055b
commit
acb657d951
|
|
@ -7,21 +7,19 @@ using System.Collections.Generic;
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A cache of metadata objects for a <see cref="DefaultModelMetadata"/>.
|
/// Holds associated metadata objects for a <see cref="DefaultModelMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// These instances are shared by all <see cref="DefaultModelMetadata"/> instances representing
|
/// Any modifications to the data must be thread-safe for multiple readers and writers.
|
||||||
/// the same <see cref="Type"/>, property, or parameter. Any modifications to the data must be
|
|
||||||
/// thread-safe for multiple readers and writers.
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class DefaultMetadataDetailsCache
|
public class DefaultMetadataDetails
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="DefaultMetadataDetailsCache"/>.
|
/// Creates a new <see cref="DefaultMetadataDetails"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The <see cref="ModelMetadataIdentity"/>.</param>
|
/// <param name="key">The <see cref="ModelMetadataIdentity"/>.</param>
|
||||||
/// <param name="attributes">The set of model attributes.</param>
|
/// <param name="attributes">The set of model attributes.</param>
|
||||||
public DefaultMetadataDetailsCache(ModelMetadataIdentity key, IReadOnlyList<object> attributes)
|
public DefaultMetadataDetails(ModelMetadataIdentity key, IReadOnlyList<object> attributes)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
Attributes = attributes;
|
Attributes = attributes;
|
||||||
|
|
@ -33,20 +31,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
public IReadOnlyList<object> Attributes { get; }
|
public IReadOnlyList<object> Attributes { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="Metadata.BindingMetadata"/>
|
/// Gets or sets the <see cref="Metadata.BindingMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BindingMetadata BindingMetadata { get; set; }
|
public BindingMetadata BindingMetadata { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="Metadata.DisplayMetadata"/>
|
/// Gets or sets the <see cref="Metadata.DisplayMetadata"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DisplayMetadata DisplayMetadata { get; set; }
|
public DisplayMetadata DisplayMetadata { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="ModelMetadataIdentity"/>
|
/// Gets or sets the <see cref="ModelMetadataIdentity"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ModelMetadataIdentity Key { get; }
|
public ModelMetadataIdentity Key { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="ModelMetadata"/> entries for the model properties.
|
||||||
|
/// </summary>
|
||||||
|
public ModelMetadata[] Properties { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a property accessor delegate to get the property value from a model object.
|
/// Gets or sets a property accessor delegate to get the property value from a model object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
private readonly IModelMetadataProvider _provider;
|
private readonly IModelMetadataProvider _provider;
|
||||||
private readonly ICompositeMetadataDetailsProvider _detailsProvider;
|
private readonly ICompositeMetadataDetailsProvider _detailsProvider;
|
||||||
private readonly DefaultMetadataDetailsCache _cache;
|
private readonly DefaultMetadataDetails _details;
|
||||||
|
|
||||||
private ReadOnlyDictionary<object, object> _additionalValues;
|
private ReadOnlyDictionary<object, object> _additionalValues;
|
||||||
private bool? _isReadOnly;
|
private bool? _isReadOnly;
|
||||||
|
|
@ -29,16 +29,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="provider">The <see cref="IModelMetadataProvider"/>.</param>
|
/// <param name="provider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||||
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
/// <param name="detailsProvider">The <see cref="ICompositeMetadataDetailsProvider"/>.</param>
|
||||||
/// <param name="cache">The <see cref="DefaultMetadataDetailsCache"/>.</param>
|
/// <param name="details">The <see cref="DefaultMetadataDetails"/>.</param>
|
||||||
public DefaultModelMetadata(
|
public DefaultModelMetadata(
|
||||||
[NotNull] IModelMetadataProvider provider,
|
[NotNull] IModelMetadataProvider provider,
|
||||||
[NotNull] ICompositeMetadataDetailsProvider detailsProvider,
|
[NotNull] ICompositeMetadataDetailsProvider detailsProvider,
|
||||||
[NotNull] DefaultMetadataDetailsCache cache)
|
[NotNull] DefaultMetadataDetails details)
|
||||||
: base(cache.Key)
|
: base(details.Key)
|
||||||
{
|
{
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
_detailsProvider = detailsProvider;
|
_detailsProvider = detailsProvider;
|
||||||
_cache = cache;
|
_details = details;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _cache.Attributes;
|
return _details.Attributes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,14 +62,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_cache.BindingMetadata == null)
|
if (_details.BindingMetadata == null)
|
||||||
{
|
{
|
||||||
var context = new BindingMetadataProviderContext(Identity, _cache.Attributes);
|
var context = new BindingMetadataProviderContext(Identity, _details.Attributes);
|
||||||
_detailsProvider.GetBindingMetadata(context);
|
_detailsProvider.GetBindingMetadata(context);
|
||||||
_cache.BindingMetadata = context.BindingMetadata;
|
_details.BindingMetadata = context.BindingMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _cache.BindingMetadata;
|
return _details.BindingMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,14 +83,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_cache.DisplayMetadata == null)
|
if (_details.DisplayMetadata == null)
|
||||||
{
|
{
|
||||||
var context = new DisplayMetadataProviderContext(Identity, _cache.Attributes);
|
var context = new DisplayMetadataProviderContext(Identity, _details.Attributes);
|
||||||
_detailsProvider.GetDisplayMetadata(context);
|
_detailsProvider.GetDisplayMetadata(context);
|
||||||
_cache.DisplayMetadata = context.DisplayMetadata;
|
_details.DisplayMetadata = context.DisplayMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _cache.DisplayMetadata;
|
return _details.DisplayMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,14 +104,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_cache.ValidationMetadata == null)
|
if (_details.ValidationMetadata == null)
|
||||||
{
|
{
|
||||||
var context = new ValidationMetadataProviderContext(Identity, _cache.Attributes);
|
var context = new ValidationMetadataProviderContext(Identity, _details.Attributes);
|
||||||
_detailsProvider.GetValidationMetadata(context);
|
_detailsProvider.GetValidationMetadata(context);
|
||||||
_cache.ValidationMetadata = context.ValidationMetadata;
|
_details.ValidationMetadata = context.ValidationMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _cache.ValidationMetadata;
|
return _details.ValidationMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,7 +286,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_isReadOnly = _cache.PropertySetter != null;
|
_isReadOnly = _details.PropertySetter != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
public class DefaultModelMetadataProvider : IModelMetadataProvider
|
public class DefaultModelMetadataProvider : IModelMetadataProvider
|
||||||
{
|
{
|
||||||
private readonly TypeCache _typeCache = new TypeCache();
|
private readonly TypeCache _typeCache = new TypeCache();
|
||||||
private readonly PropertiesCache _propertiesCache = new PropertiesCache();
|
private readonly Func<ModelMetadataIdentity, ModelMetadataCacheEntry> _cacheEntryFactory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="DefaultModelMetadataProvider"/>.
|
/// Creates a new <see cref="DefaultModelMetadataProvider"/>.
|
||||||
|
|
@ -24,6 +24,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
public DefaultModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider)
|
public DefaultModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider)
|
||||||
{
|
{
|
||||||
DetailsProvider = detailsProvider;
|
DetailsProvider = detailsProvider;
|
||||||
|
|
||||||
|
_cacheEntryFactory = CreateCacheEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -36,15 +38,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
var key = ModelMetadataIdentity.ForType(modelType);
|
var key = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
|
||||||
var propertyEntries = _propertiesCache.GetOrAdd(key, CreatePropertyCacheEntries);
|
var cacheEntry = _typeCache.GetOrAdd(key, _cacheEntryFactory);
|
||||||
|
|
||||||
var properties = new ModelMetadata[propertyEntries.Length];
|
// We're relying on a safe race-condition for Properties - take care only
|
||||||
for (var i = 0; i < properties.Length; i++)
|
// to set the value onces the properties are fully-initialized.
|
||||||
|
if (cacheEntry.Details.Properties == null)
|
||||||
{
|
{
|
||||||
properties[i] = CreateModelMetadata(propertyEntries[i]);
|
var propertyDetails = CreatePropertyDetails(key);
|
||||||
|
|
||||||
|
var properties = new ModelMetadata[propertyDetails.Length];
|
||||||
|
for (var i = 0; i < properties.Length; i++)
|
||||||
|
{
|
||||||
|
properties[i] = CreateModelMetadata(propertyDetails[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheEntry.Details.Properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
return properties;
|
return cacheEntry.Details.Properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -52,75 +63,53 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
{
|
{
|
||||||
var key = ModelMetadataIdentity.ForType(modelType);
|
var key = ModelMetadataIdentity.ForType(modelType);
|
||||||
|
|
||||||
var entry = _typeCache.GetOrAdd(key, CreateTypeCacheEntry);
|
var cacheEntry = _typeCache.GetOrAdd(key, _cacheEntryFactory);
|
||||||
return CreateModelMetadata(entry);
|
return cacheEntry.Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelMetadataCacheEntry CreateCacheEntry(ModelMetadataIdentity key)
|
||||||
|
{
|
||||||
|
var details = CreateTypeDetails(key);
|
||||||
|
var metadata = CreateModelMetadata(details);
|
||||||
|
return new ModelMetadataCacheEntry(metadata, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ModelMetadata"/> from a <see cref="DefaultMetadataDetailsCache"/>.
|
/// Creates a new <see cref="ModelMetadata"/> from a <see cref="DefaultMetadataDetails"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entry">The <see cref="DefaultMetadataDetailsCache"/> entry with cached data.</param>
|
/// <param name="entry">The <see cref="DefaultMetadataDetails"/> entry with cached data.</param>
|
||||||
/// <returns>A new <see cref="ModelMetadata"/> instance.</returns>
|
/// <returns>A new <see cref="ModelMetadata"/> instance.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <see cref="DefaultModelMetadataProvider"/> will always create instances of
|
/// <see cref="DefaultModelMetadataProvider"/> will always create instances of
|
||||||
/// <see cref="DefaultModelMetadata"/> .Override this method to create a <see cref="ModelMetadata"/>
|
/// <see cref="DefaultModelMetadata"/> .Override this method to create a <see cref="ModelMetadata"/>
|
||||||
/// of a different concrete type.
|
/// of a different concrete type.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetailsCache entry)
|
protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry)
|
||||||
{
|
{
|
||||||
return new DefaultModelMetadata(this, DetailsProvider, entry);
|
return new DefaultModelMetadata(this, DetailsProvider, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <see cref="DefaultMetadataDetailsCache"/> entries for the properties of a model
|
/// Creates the <see cref="DefaultMetadataDetails"/> entries for the properties of a model
|
||||||
/// <see cref="Type"/>.
|
/// <see cref="Type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">
|
/// <param name="key">
|
||||||
/// The <see cref="ModelMetadataIdentity"/> identifying the model <see cref="Type"/>.
|
/// The <see cref="ModelMetadataIdentity"/> identifying the model <see cref="Type"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>A cache object for each property of the model <see cref="Type"/>.</returns>
|
/// <returns>A details object for each property of the model <see cref="Type"/>.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The results of this method will be cached and used to satisfy calls to
|
/// The results of this method will be cached and used to satisfy calls to
|
||||||
/// <see cref="GetMetadataForProperties(Type)"/>. Override this method to provide a different
|
/// <see cref="GetMetadataForProperties(Type)"/>. Override this method to provide a different
|
||||||
/// set of property data.
|
/// set of property data.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual DefaultMetadataDetailsCache[] CreatePropertyCacheEntries([NotNull] ModelMetadataIdentity key)
|
protected virtual DefaultMetadataDetails[] CreatePropertyDetails([NotNull] ModelMetadataIdentity key)
|
||||||
{
|
{
|
||||||
var propertyHelpers = PropertyHelper.GetProperties(key.ModelType);
|
var propertyHelpers = PropertyHelper.GetVisibleProperties(key.ModelType);
|
||||||
|
|
||||||
var propertyEntries = new List<DefaultMetadataDetailsCache>(propertyHelpers.Length);
|
var propertyEntries = new List<DefaultMetadataDetails>(propertyHelpers.Length);
|
||||||
for (var i = 0; i < propertyHelpers.Length; i++)
|
for (var i = 0; i < propertyHelpers.Length; i++)
|
||||||
{
|
{
|
||||||
var propertyHelper = propertyHelpers[i];
|
var propertyHelper = propertyHelpers[i];
|
||||||
if (propertyHelper.Property.DeclaringType != key.ModelType)
|
|
||||||
{
|
|
||||||
// If this property was declared on a base type then look for the definition closest to the
|
|
||||||
// the model type to see if we should include it.
|
|
||||||
var ignoreProperty = false;
|
|
||||||
|
|
||||||
// Walk up the hierarchy until we find the type that actally declares this
|
|
||||||
// PropertyInfo.
|
|
||||||
var currentType = key.ModelType.GetTypeInfo();
|
|
||||||
while (currentType != propertyHelper.Property.DeclaringType.GetTypeInfo())
|
|
||||||
{
|
|
||||||
// We've found a 'more proximal' public definition
|
|
||||||
var declaredProperty = currentType.GetDeclaredProperty(propertyHelper.Name);
|
|
||||||
if (declaredProperty != null)
|
|
||||||
{
|
|
||||||
ignoreProperty = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentType = currentType.BaseType.GetTypeInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ignoreProperty)
|
|
||||||
{
|
|
||||||
// There's a better definition, ignore this.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var propertyKey = ModelMetadataIdentity.ForProperty(
|
var propertyKey = ModelMetadataIdentity.ForProperty(
|
||||||
propertyHelper.Property.PropertyType,
|
propertyHelper.Property.PropertyType,
|
||||||
propertyHelper.Name,
|
propertyHelper.Name,
|
||||||
|
|
@ -130,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
key.ModelType,
|
key.ModelType,
|
||||||
propertyHelper.Property));
|
propertyHelper.Property));
|
||||||
|
|
||||||
var propertyEntry = new DefaultMetadataDetailsCache(propertyKey, attributes);
|
var propertyEntry = new DefaultMetadataDetails(propertyKey, attributes);
|
||||||
if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPrivate == true)
|
if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPrivate == true)
|
||||||
{
|
{
|
||||||
propertyEntry.PropertyAccessor = PropertyHelper.MakeFastPropertyGetter(propertyHelper.Property);
|
propertyEntry.PropertyAccessor = PropertyHelper.MakeFastPropertyGetter(propertyHelper.Property);
|
||||||
|
|
@ -148,24 +137,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the <see cref="DefaultMetadataDetailsCache"/> entry for a model <see cref="Type"/>.
|
/// Creates the <see cref="DefaultMetadataDetails"/> entry for a model <see cref="Type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">
|
/// <param name="key">
|
||||||
/// The <see cref="ModelMetadataIdentity"/> identifying the model <see cref="Type"/>.
|
/// The <see cref="ModelMetadataIdentity"/> identifying the model <see cref="Type"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>A cache object for the model <see cref="Type"/>.</returns>
|
/// <returns>A details object for the model <see cref="Type"/>.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The results of this method will be cached and used to satisfy calls to
|
/// The results of this method will be cached and used to satisfy calls to
|
||||||
/// <see cref="GetMetadataForType(Type)"/>. Override this method to provide a different
|
/// <see cref="GetMetadataForType(Type)"/>. Override this method to provide a different
|
||||||
/// set of attributes.
|
/// set of attributes.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual DefaultMetadataDetailsCache CreateTypeCacheEntry([NotNull] ModelMetadataIdentity key)
|
protected virtual DefaultMetadataDetails CreateTypeDetails([NotNull] ModelMetadataIdentity key)
|
||||||
{
|
{
|
||||||
var attributes = new List<object>(ModelAttributes.GetAttributesForType(key.ModelType));
|
var attributes = new List<object>(ModelAttributes.GetAttributesForType(key.ModelType));
|
||||||
return new DefaultMetadataDetailsCache(key, attributes);
|
return new DefaultMetadataDetails(key, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache>
|
private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, ModelMetadataCacheEntry>
|
||||||
{
|
{
|
||||||
public TypeCache()
|
public TypeCache()
|
||||||
: base(ModelMetadataIdentityComparer.Instance)
|
: base(ModelMetadataIdentityComparer.Instance)
|
||||||
|
|
@ -173,12 +162,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PropertiesCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache[]>
|
private struct ModelMetadataCacheEntry
|
||||||
{
|
{
|
||||||
public PropertiesCache()
|
public ModelMetadataCacheEntry(ModelMetadata metadata, DefaultMetadataDetails details)
|
||||||
: base(ModelMetadataIdentityComparer.Instance)
|
|
||||||
{
|
{
|
||||||
|
Metadata = metadata;
|
||||||
|
Details = details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ModelMetadata Metadata { get; private set; }
|
||||||
|
|
||||||
|
public DefaultMetadataDetails Details { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ModelMetadataIdentityComparer : IEqualityComparer<ModelMetadataIdentity>
|
private class ModelMetadataIdentityComparer : IEqualityComparer<ModelMetadataIdentity>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
Assert.Equal("OnType", attribute.Value);
|
Assert.Equal("OnType", attribute.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The attributes and other 'details' are cached
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetMetadataForType_Cached()
|
public void GetMetadataForType_Cached()
|
||||||
{
|
{
|
||||||
|
|
@ -38,6 +37,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var metadata2 = Assert.IsType<DefaultModelMetadata>(provider.GetMetadataForType(typeof(ModelType)));
|
var metadata2 = Assert.IsType<DefaultModelMetadata>(provider.GetMetadataForType(typeof(ModelType)));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
Assert.Same(metadata1, metadata2);
|
||||||
Assert.Same(metadata1.Attributes, metadata2.Attributes);
|
Assert.Same(metadata1.Attributes, metadata2.Attributes);
|
||||||
Assert.Same(metadata1.BindingMetadata, metadata2.BindingMetadata);
|
Assert.Same(metadata1.BindingMetadata, metadata2.BindingMetadata);
|
||||||
Assert.Same(metadata1.DisplayMetadata, metadata2.DisplayMetadata);
|
Assert.Same(metadata1.DisplayMetadata, metadata2.DisplayMetadata);
|
||||||
|
|
@ -80,19 +80,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var provider = CreateProvider();
|
var provider = CreateProvider();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata1 = provider.GetMetadataForProperties(typeof(ModelType)).Cast<DefaultModelMetadata>().ToArray();
|
var properties1 = provider.GetMetadataForProperties(typeof(ModelType)).Cast<DefaultModelMetadata>().ToArray();
|
||||||
var metadata2 = provider.GetMetadataForProperties(typeof(ModelType)).Cast<DefaultModelMetadata>().ToArray();
|
var properties2 = provider.GetMetadataForProperties(typeof(ModelType)).Cast<DefaultModelMetadata>().ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
for (var i = 0; i < metadata1.Length; i++)
|
Assert.Equal(properties1.Length, properties2.Length);
|
||||||
|
for (var i = 0; i < properties1.Length; i++)
|
||||||
{
|
{
|
||||||
Assert.Same(metadata1[i].Attributes, metadata2[i].Attributes);
|
Assert.Same(properties1[i], properties2[i]);
|
||||||
Assert.Same(metadata1[i].BindingMetadata, metadata2[i].BindingMetadata);
|
Assert.Same(properties1[i].Attributes, properties2[i].Attributes);
|
||||||
Assert.Same(metadata1[i].DisplayMetadata, metadata2[i].DisplayMetadata);
|
Assert.Same(properties1[i].BindingMetadata, properties2[i].BindingMetadata);
|
||||||
Assert.Same(metadata1[i].ValidationMetadata, metadata2[i].ValidationMetadata);
|
Assert.Same(properties1[i].DisplayMetadata, properties2[i].DisplayMetadata);
|
||||||
|
Assert.Same(properties1[i].ValidationMetadata, properties2[i].ValidationMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForType_PropertiesCollection_Cached()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = CreateProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var metadata1 = Assert.IsType<DefaultModelMetadata>(provider.GetMetadataForType(typeof(ModelType)));
|
||||||
|
var metadata2 = Assert.IsType<DefaultModelMetadata>(provider.GetMetadataForType(typeof(ModelType)));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(metadata1.Properties, metadata2.Properties);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetMetadataForProperties_IncludesMergedAttributes()
|
public void GetMetadataForProperties_IncludesMergedAttributes()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
Enumerable.Empty<IMetadataDetailsProvider>());
|
Enumerable.Empty<IMetadataDetailsProvider>());
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
Enumerable.Empty<IMetadataDetailsProvider>());
|
Enumerable.Empty<IMetadataDetailsProvider>());
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(Exception));
|
var key = ModelMetadataIdentity.ForType(typeof(Exception));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForProperty(typeof(string), "Message", typeof(Exception));
|
var key = ModelMetadataIdentity.ForProperty(typeof(string), "Message", typeof(Exception));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(modelType);
|
var key = ModelMetadataIdentity.ForType(modelType);
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(modelType);
|
var key = ModelMetadataIdentity.ForType(modelType);
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
@ -162,13 +162,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
new DefaultModelMetadata(
|
new DefaultModelMetadata(
|
||||||
provider.Object,
|
provider.Object,
|
||||||
detailsProvider,
|
detailsProvider,
|
||||||
new DefaultMetadataDetailsCache(
|
new DefaultMetadataDetails(
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Prop1", typeof(string)),
|
||||||
attributes: null)),
|
attributes: null)),
|
||||||
new DefaultModelMetadata(
|
new DefaultModelMetadata(
|
||||||
provider.Object,
|
provider.Object,
|
||||||
detailsProvider,
|
detailsProvider,
|
||||||
new DefaultMetadataDetailsCache(
|
new DefaultMetadataDetails(
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), "Prop2", typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), "Prop2", typeof(string)),
|
||||||
attributes: null)),
|
attributes: null)),
|
||||||
};
|
};
|
||||||
|
|
@ -178,7 +178,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
.Returns(expectedProperties);
|
.Returns(expectedProperties);
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
expectedProperties.Add(new DefaultModelMetadata(
|
expectedProperties.Add(new DefaultModelMetadata(
|
||||||
provider.Object,
|
provider.Object,
|
||||||
detailsProvider,
|
detailsProvider,
|
||||||
new DefaultMetadataDetailsCache(
|
new DefaultMetadataDetails(
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), originalName, typeof(string)),
|
||||||
attributes: null)));
|
attributes: null)));
|
||||||
}
|
}
|
||||||
|
|
@ -248,7 +248,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
.Returns(expectedProperties);
|
.Returns(expectedProperties);
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
@ -338,7 +338,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var expectedProperties = new List<DefaultModelMetadata>();
|
var expectedProperties = new List<DefaultModelMetadata>();
|
||||||
foreach (var kvp in originalNamesAndOrders)
|
foreach (var kvp in originalNamesAndOrders)
|
||||||
{
|
{
|
||||||
var propertyCache = new DefaultMetadataDetailsCache(
|
var propertyCache = new DefaultMetadataDetails(
|
||||||
ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)),
|
ModelMetadataIdentity.ForProperty(typeof(int), kvp.Key, typeof(string)),
|
||||||
attributes: null);
|
attributes: null);
|
||||||
|
|
||||||
|
|
@ -356,7 +356,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
.Returns(expectedProperties);
|
.Returns(expectedProperties);
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider.Object, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
@ -377,7 +377,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
@ -398,7 +398,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
var detailsProvider = new EmptyCompositeMetadataDetailsProvider();
|
||||||
|
|
||||||
var key = ModelMetadataIdentity.ForType(typeof(string));
|
var key = ModelMetadataIdentity.ForType(typeof(string));
|
||||||
var cache = new DefaultMetadataDetailsCache(key, new object[0]);
|
var cache = new DefaultMetadataDetails(key, new object[0]);
|
||||||
|
|
||||||
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -838,10 +838,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
|
||||||
_attributes = attributes;
|
_attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DefaultMetadataDetailsCache CreateTypeCacheEntry(ModelMetadataIdentity key)
|
protected override DefaultMetadataDetails CreateTypeDetails([NotNull]ModelMetadataIdentity key)
|
||||||
{
|
{
|
||||||
var entry = base.CreateTypeCacheEntry(key);
|
var entry = base.CreateTypeDetails(key);
|
||||||
return new DefaultMetadataDetailsCache(key, _attributes.Concat(entry.Attributes).ToArray());
|
return new DefaultMetadataDetails(key, _attributes.Concat(entry.Attributes).ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue