//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * Cache class * * Copyright (c) 1999 Microsoft Corporation */ namespace System.Web.Caching { using System.Collections; using System.Collections.Specialized; using System.Configuration; using System.Configuration.Provider; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Web.Util; using System.Web; using Microsoft.Win32; using System.Security.Permissions; using System.Globalization; using System.Web.Configuration; using System.Web.Hosting; using System.Web.Management; using Debug = System.Web.Util.Debug; /// /// Represents the method that will handle the /// event of a System.Web.Caching.Cache instance. /// public delegate void CacheItemRemovedCallback( string key, object value, CacheItemRemovedReason reason); /// /// Represents the method that will handle the /// event of a System.Web.Caching.Cache instance. /// [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", Justification="Shipped this way in NetFx 2.0 SP2")] public delegate void CacheItemUpdateCallback( string key, CacheItemUpdateReason reason, out object expensiveObject, out CacheDependency dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration); /// /// Specifies the relative priority of items stored in the System.Web.Caching.Cache. When the Web /// server runs low on memory, the Cache selectively purges items to free system /// memory. Items with higher priorities are less likely to be removed from the /// cache when the server is under load. Web /// applications can use these /// values to prioritize cached items relative to one another. The default is /// normal. /// public enum CacheItemPriority { /// /// The cahce items with this priority level will be the first /// to be removed when the server frees system memory by deleting items from the /// cache. /// Low = 1, /// /// The cache items with this priority level /// are in the second group to be removed when the server frees system memory by /// deleting items from the cache. /// BelowNormal, /// /// The cache items with this priority level are in /// the third group to be removed when the server frees system memory by deleting items from the cache. This is the default. /// Normal, /// /// The cache items with this priority level are in the /// fourth group to be removed when the server frees system memory by deleting items from the /// cache. /// AboveNormal, /// /// The cache items with this priority level are in the fifth group to be removed /// when the server frees system memory by deleting items from the cache. /// High, /// /// The cache items with this priority level will not be removed when the server /// frees system memory by deleting items from the cache. /// NotRemovable, /// /// The default value is Normal. /// Default = Normal } /// /// Specifies the reason that a cached item was removed. /// public enum CacheItemRemovedReason { /// /// The item was removed from the cache by the 'System.Web.Caching.Cache.Remove' method, or by an System.Web.Caching.Cache.Insert method call specifying the same key. /// Removed = 1, /// /// The item was removed from the cache because it expired. /// Expired, /// /// The item was removed from the cache because the value in the hitInterval /// parameter was not met, or because the system removed it to free memory. /// Underused, /// /// The item was removed from the cache because a file or key dependency was /// changed. /// DependencyChanged } /// /// Specifies the reason why a cached item needs to be updated. /// [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "This enum should mirror CacheItemRemovedReason enum in design")] public enum CacheItemUpdateReason { /// /// The item needs to be updated because it expired. /// Expired = 1, /// /// The item needs to be updated because a file or key dependency was /// changed. /// DependencyChanged } /// /// Implements the cache for a Web application. There is only one instance of /// this class per application domain, and it remains valid only as long as the /// application domain remains active. Information about an instance of this class /// is available through the property of the System.Web.HttpContext. /// // // Extra notes: // - The Cache object contains a ICacheStore object and wraps it for public consumption. // public sealed class Cache : IEnumerable { /// /// Sets the absolute expiration policy to, in essence, /// never. When set, this field is equal to the the System.DateTime.MaxValue , which is a constant /// representing the largest possible value. The maximum date and /// time value is equivilant to "12/31/9999 11:59:59 PM". This field is read-only. /// public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue; /// /// Sets the amount of time for sliding cache expirations to /// zero. When set, this field is equal to the System.TimeSpan.Zero field, which is a constant value of /// zero. This field is read-only. /// public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero; static CacheStoreProvider _objectCache = null; static CacheStoreProvider _internalCache = null; static CacheItemRemovedCallback s_sentinelRemovedCallback = new CacheItemRemovedCallback(SentinelEntry.OnCacheItemRemovedCallback); /// /// /// This constructor is for internal use only, and was accidentally made public - do not use. /// [SecurityPermission(SecurityAction.Demand, Unrestricted=true)] public Cache() { } // // internal ctor used by CacheCommon that avoids the demand for UnmanagedCode. // internal Cache(int dummy) { } /// /// Gets the number of items stored in the cache. This value can be useful when /// monitoring your application's performance or when using the ASP.NET tracing /// functionality. /// public int Count { get { return Convert.ToInt32(ObjectCache.ItemCount); } } internal CacheStoreProvider GetInternalCache(bool createIfDoesNotExist) { if (_internalCache == null && createIfDoesNotExist) { lock (this) { if (_internalCache == null) { NameValueCollection cacheProviderSettings = HostingEnvironment.CacheStoreProviderSettings; if (cacheProviderSettings != null) { string providerName = (string)cacheProviderSettings["name"]; // Grab this now, as InstantiateProvider will remove it from settings cacheProviderSettings["isPublic"] = "false"; _internalCache = (CacheStoreProvider)ProvidersHelper.InstantiateProvider(cacheProviderSettings, typeof(CacheStoreProvider)); _internalCache.Initialize(providerName, cacheProviderSettings); } else { if (_objectCache is AspNetCache) { _internalCache = new AspNetCache((AspNetCache)_objectCache, isPublic: false); } else { _internalCache = new AspNetCache(isPublic: false); } _internalCache.Initialize(null, new NameValueCollection()); } } } } return _internalCache; } [PermissionSet(SecurityAction.Assert, Unrestricted = true)] [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "We carefully control this method's callers.")] internal CacheStoreProvider GetObjectCache(bool createIfDoesNotExist) { if (_objectCache == null && createIfDoesNotExist) { lock (this) { if (_objectCache == null) { NameValueCollection cacheProviderSettings = HostingEnvironment.CacheStoreProviderSettings; if (cacheProviderSettings != null) { string providerName = (string)cacheProviderSettings["name"]; // Grab this now, as InstantiateProvider will remove it from settings cacheProviderSettings["isPublic"] = "true"; _objectCache = (CacheStoreProvider)ProvidersHelper.InstantiateProvider(cacheProviderSettings, typeof(CacheStoreProvider)); _objectCache.Initialize(providerName, cacheProviderSettings); } else { if (_internalCache is AspNetCache) { _objectCache = new AspNetCache((AspNetCache)_internalCache, isPublic: true); } else { _objectCache = new AspNetCache(isPublic: true); } _objectCache.Initialize(null, new NameValueCollection()); } } } } return _objectCache; } /// /// Provides access to the cache store used by ASP.Net internals. /// internal CacheStoreProvider InternalCache { get { return GetInternalCache(createIfDoesNotExist: true); } } /// /// Provides access to the cache store that backs HttpRuntime.Cache. /// internal CacheStoreProvider ObjectCache { get { return GetObjectCache(createIfDoesNotExist: true); } } /// IEnumerator IEnumerable.GetEnumerator() { return ObjectCache.GetEnumerator(); } /// /// Returns a dictionary enumerator used for iterating through the key/value /// pairs contained in the cache. Items can be added to or removed from the cache /// while this method is enumerating through the cache items. /// public IDictionaryEnumerator GetEnumerator() { return ObjectCache.GetEnumerator(); } /// /// Gets or sets an item in the cache. /// public object this[string key] { get { return Get(key); } set { Insert(key, value); } } private class SentinelEntry { private string _key; private CacheDependency _expensiveObjectDependency; private CacheItemUpdateCallback _cacheItemUpdateCallback; public SentinelEntry(string key, CacheDependency expensiveObjectDependency, CacheItemUpdateCallback callback) { _key = key; _expensiveObjectDependency = expensiveObjectDependency; _cacheItemUpdateCallback = callback; } public string Key { get { return _key; } } public CacheDependency ExpensiveObjectDependency { get { return _expensiveObjectDependency; } } public CacheItemUpdateCallback CacheItemUpdateCallback { get { return _cacheItemUpdateCallback; } } public static void OnCacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason) { CacheItemUpdateReason updateReason; SentinelEntry entry = value as SentinelEntry; switch (reason) { case CacheItemRemovedReason.Expired: updateReason = CacheItemUpdateReason.Expired; break; case CacheItemRemovedReason.DependencyChanged: updateReason = CacheItemUpdateReason.DependencyChanged; if (entry.ExpensiveObjectDependency.HasChanged) { // If the expensiveObject has been removed explicitly by Cache.Remove, // return from the SentinelEntry removed callback // thus effectively removing the SentinelEntry from the cache. return; } break; case CacheItemRemovedReason.Underused: Debug.Fail("Reason should never be CacheItemRemovedReason.Underused since the entry was inserted as NotRemovable."); return; default: // do nothing if reason is Removed return; } CacheDependency cacheDependency; DateTime absoluteExpiration; TimeSpan slidingExpiration; object expensiveObject; CacheItemUpdateCallback callback = entry.CacheItemUpdateCallback; // invoke update callback try { callback(entry.Key, updateReason, out expensiveObject, out cacheDependency, out absoluteExpiration, out slidingExpiration); // Dev10 861163 - Only update the "expensive" object if the user returns a new object and the // cache dependency hasn't changed. (Inserting with a cache dependency that has already changed will cause recursion.) if (expensiveObject != null && (cacheDependency == null || !cacheDependency.HasChanged)) { HttpRuntime.Cache.Insert(entry.Key, expensiveObject, cacheDependency, absoluteExpiration, slidingExpiration, entry.CacheItemUpdateCallback); } else { HttpRuntime.Cache.Remove(entry.Key); } } catch (Exception e) { HttpRuntime.Cache.Remove(entry.Key); try { WebBaseEvent.RaiseRuntimeError(e, value); } catch { } } } } /// /// Retrieves an item from the cache. /// public object Get(string key) { return ObjectCache.Get(key); } /// /// Inserts an item into the Cache with default values. /// public void Insert(string key, object value) { ObjectCache.Insert(key, value, options: null); } /// /// Inserts an object into the System.Web.Caching.Cache that has file or key /// dependencies. /// public void Insert(string key, object value, CacheDependency dependencies) { ObjectCache.Insert(key, value, new CacheInsertOptions() { Dependencies = dependencies }); } /// /// Inserts an object into the System.Web.Caching.Cache that has file or key dependencies and /// expires at the value set in the parameter. /// public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration) { DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration); ObjectCache.Insert(key, value, new CacheInsertOptions() { Dependencies = dependencies, AbsoluteExpiration = absoluteExpiration, SlidingExpiration = slidingExpiration }); } public void Insert( string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback) { DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration); ObjectCache.Insert(key, value, new CacheInsertOptions() { Dependencies = dependencies, AbsoluteExpiration = utcAbsoluteExpiration, SlidingExpiration = slidingExpiration, Priority = priority, OnRemovedCallback = onRemoveCallback }); } // DevDiv Bugs 162763: // Add a an event that fires *before* an item is evicted from the ASP.NET Cache public void Insert( string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemUpdateCallback onUpdateCallback) { if (dependencies == null && absoluteExpiration == Cache.NoAbsoluteExpiration && slidingExpiration == Cache.NoSlidingExpiration) { throw new ArgumentException(SR.GetString(SR.Invalid_Parameters_To_Insert)); } if (onUpdateCallback == null) { throw new ArgumentNullException("onUpdateCallback"); } DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration); // Insert updatable cache entry ObjectCache.Insert(key, value, new CacheInsertOptions() { Priority = CacheItemPriority.NotRemovable }); // Ensure the sentinel depends on its updatable entry string[] cacheKeys = { key }; CacheDependency expensiveObjectDep = new CacheDependency(null, cacheKeys); if (dependencies == null) { dependencies = expensiveObjectDep; } else { AggregateCacheDependency deps = new AggregateCacheDependency(); deps.Add(dependencies, expensiveObjectDep); dependencies = deps; } // Insert sentinel entry for the updatable cache entry HttpRuntime.Cache.InternalCache.Insert( CacheInternal.PrefixValidationSentinel + key, new SentinelEntry(key, expensiveObjectDep, onUpdateCallback), new CacheInsertOptions() { Dependencies = dependencies, AbsoluteExpiration = utcAbsoluteExpiration, SlidingExpiration = slidingExpiration, Priority = CacheItemPriority.NotRemovable, OnRemovedCallback = Cache.s_sentinelRemovedCallback }); } public object Add( string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback) { DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration); return ObjectCache.Add(key, value, new CacheInsertOptions() { Dependencies = dependencies, AbsoluteExpiration = utcAbsoluteExpiration, SlidingExpiration = slidingExpiration, Priority = priority, OnRemovedCallback = onRemoveCallback }); } /// /// Removes the specified item from the cache. /// public object Remove(string key) { return ObjectCache.Remove(key, CacheItemRemovedReason.Removed); } public long EffectivePrivateBytesLimit { get { return AspNetMemoryMonitor.ProcessPrivateBytesLimit; } } public long EffectivePercentagePhysicalMemoryLimit { get { return AspNetMemoryMonitor.PhysicalMemoryPercentageLimit; } } } }