// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.Caching; namespace System.Web.Mvc { public abstract class CachedAssociatedMetadataProvider : AssociatedMetadataProvider where TModelMetadata : ModelMetadata { private static ConcurrentDictionary _typeIds = new ConcurrentDictionary(); private string _cacheKeyPrefix; private CacheItemPolicy _cacheItemPolicy = new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(20) }; private ObjectCache _prototypeCache; protected internal CacheItemPolicy CacheItemPolicy { get { return _cacheItemPolicy; } set { _cacheItemPolicy = value; } } protected string CacheKeyPrefix { get { if (_cacheKeyPrefix == null) { _cacheKeyPrefix = "MetadataPrototypes::" + GetType().GUID.ToString("B"); } return _cacheKeyPrefix; } } protected internal ObjectCache PrototypeCache { get { return _prototypeCache ?? MemoryCache.Default; } set { _prototypeCache = value; } } protected sealed override ModelMetadata CreateMetadata(IEnumerable attributes, Type containerType, Func modelAccessor, Type modelType, string propertyName) { // If metadata is being created for a property then containerType != null && propertyName != null // If metadata is being created for a type then containerType == null && propertyName == null, so we have to use modelType for the cache key. Type typeForCache = containerType ?? modelType; string cacheKey = GetCacheKey(typeForCache, propertyName); TModelMetadata prototype = PrototypeCache.Get(cacheKey) as TModelMetadata; if (prototype == null) { prototype = CreateMetadataPrototype(attributes, containerType, modelType, propertyName); PrototypeCache.Add(cacheKey, prototype, CacheItemPolicy); } return CreateMetadataFromPrototype(prototype, modelAccessor); } // New override for creating the prototype metadata (without the accessor) protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable attributes, Type containerType, Type modelType, string propertyName); // New override for applying the prototype + modelAccess to yield the final metadata protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype, Func modelAccessor); internal string GetCacheKey(Type type, string propertyName = null) { propertyName = propertyName ?? String.Empty; return CacheKeyPrefix + GetTypeId(type) + propertyName; } public sealed override ModelMetadata GetMetadataForProperty(Func modelAccessor, Type containerType, string propertyName) { return base.GetMetadataForProperty(modelAccessor, containerType, propertyName); } protected sealed override ModelMetadata GetMetadataForProperty(Func modelAccessor, Type containerType, PropertyDescriptor propertyDescriptor) { return base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor); } public sealed override IEnumerable GetMetadataForProperties(object container, Type containerType) { return base.GetMetadataForProperties(container, containerType); } public sealed override ModelMetadata GetMetadataForType(Func modelAccessor, Type modelType) { return base.GetMetadataForType(modelAccessor, modelType); } private static string GetTypeId(Type type) { // It's fine using a random Guid since we store the mapping for types to guids. return _typeIds.GetOrAdd(type, _ => Guid.NewGuid().ToString("B")); } } }