File: Cache\cache.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="cache.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
/*
 * 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;
 
 
    /// <devdoc>
    /// <para>Represents the method that will handle the <see langword='onRemoveCallback'/>
    /// event of a System.Web.Caching.Cache instance.</para>
    /// </devdoc>
    public delegate void CacheItemRemovedCallback(
            string key, object value, CacheItemRemovedReason reason);
 
    /// <devdoc>
    /// <para>Represents the method that will handle the <see langword='onUpdateCallback'/>
    /// event of a System.Web.Caching.Cache instance.</para>
    /// </devdoc>
    [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);
 
    /// <devdoc>
    /// <para> 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.</para>
    /// </devdoc>
    public enum CacheItemPriority {
 
        /// <devdoc>
        ///    <para> 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.</para>
        /// </devdoc>
        Low = 1,
 
        /// <devdoc>
        ///    <para> 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. </para>
        /// </devdoc>
        BelowNormal,
 
        /// <devdoc>
        ///    <para> 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. </para>
        /// </devdoc>
        Normal,
 
        /// <devdoc>
        ///    <para> 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. </para>
        /// </devdoc>
        AboveNormal,
 
        /// <devdoc>
        ///    <para>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. </para>
        /// </devdoc>
        High,
 
        /// <devdoc>
        ///    <para>The cache items with this priority level will not be removed when the server
        ///       frees system memory by deleting items from the cache. </para>
        /// </devdoc>
        NotRemovable,
 
        /// <devdoc>
        ///    <para>The default value is Normal.</para>
        /// </devdoc>
        Default = Normal
    }
 
 
    /// <devdoc>
    ///    <para>Specifies the reason that a cached item was removed.</para>
    /// </devdoc>
    public enum CacheItemRemovedReason {
 
        /// <devdoc>
        /// <para>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.</para>
        /// </devdoc>
        Removed = 1,
 
        /// <devdoc>
        ///    <para>The item was removed from the cache because it expired. </para>
        /// </devdoc>
        Expired,
 
        /// <devdoc>
        ///    <para>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.</para>
        /// </devdoc>
        Underused,
 
        /// <devdoc>
        ///    <para>The item was removed from the cache because a file or key dependency was
        ///       changed.</para>
        /// </devdoc>
        DependencyChanged
    }
 
    /// <devdoc>
    ///    <para>Specifies the reason why a cached item needs to be updated.</para>
    /// </devdoc>
    [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue",
            Justification = "This enum should mirror CacheItemRemovedReason enum in design")]
    public enum CacheItemUpdateReason {
 
        /// <devdoc>
        ///    <para>The item needs to be updated because it expired. </para>
        /// </devdoc>
        Expired = 1,
 
        /// <devdoc>
        ///    <para>The item needs to be updated because a file or key dependency was
        ///       changed.</para>
        /// </devdoc>
        DependencyChanged
    }
 
    /// <devdoc>
    ///    <para>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 <see langword='Cache'/> property of the System.Web.HttpContext.</para>
    /// </devdoc>
 
    //
    // Extra notes:
    // - The Cache object contains a ICacheStore object and wraps it for public consumption.
    //
    public sealed class Cache : IEnumerable {
 
        /// <devdoc>
        ///    <para>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 <see langword='DateTime'/> value. The maximum date and
        ///       time value is equivilant to "12/31/9999 11:59:59 PM". This field is read-only.</para>
        /// </devdoc>
        public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
 
 
        /// <devdoc>
        ///    <para>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.</para>
        /// </devdoc>
        public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
 
        static CacheStoreProvider   _objectCache = null;
        static CacheStoreProvider   _internalCache = null;
        static CacheItemRemovedCallback s_sentinelRemovedCallback = new CacheItemRemovedCallback(SentinelEntry.OnCacheItemRemovedCallback);
 
        /// <internalonly/>
        /// <devdoc>
        ///    <para>This constructor is for internal use only, and was accidentally made public - do not use.</para>
        /// </devdoc>
        [SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
        public Cache() {
        }
 
        //
        // internal ctor used by CacheCommon that avoids the demand for UnmanagedCode.
        //
        internal Cache(int dummy) {
        }
 
 
        /// <devdoc>
        ///    <para>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.</para>
        /// </devdoc>
        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;
        }
 
        /// <devdoc>
        ///    <para>Provides access to the cache store used by ASP.Net internals.</para>
        /// </devdoc>
        internal CacheStoreProvider InternalCache {
            get { return GetInternalCache(createIfDoesNotExist: true); }
        }
 
        /// <devdoc>
        ///    <para>Provides access to the cache store that backs HttpRuntime.Cache.</para>
        /// </devdoc>
        internal CacheStoreProvider ObjectCache {
            get { return GetObjectCache(createIfDoesNotExist: true); }
        }
 
        /// <internalonly/>
        IEnumerator IEnumerable.GetEnumerator() {
            return ObjectCache.GetEnumerator();
        }
 
 
        /// <devdoc>
        ///    <para>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.</para>
        /// </devdoc>
        public IDictionaryEnumerator GetEnumerator() {
            return ObjectCache.GetEnumerator();
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets an item in the cache.</para>
        /// </devdoc>
        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 {
                    }
                }
            }
        }
 
        /// <devdoc>
        ///    <para>Retrieves an item from the cache.</para>
        /// </devdoc>
        public object Get(string key) {
            return ObjectCache.Get(key);
        }
 
 
        /// <devdoc>
        ///    <para>Inserts an item into the Cache with default values.</para>
        /// </devdoc>
        public void Insert(string key, object value) {
            ObjectCache.Insert(key, value, options: null);
        }
 
 
        /// <devdoc>
        /// <para>Inserts an object into the System.Web.Caching.Cache that has file or key
        ///    dependencies.</para>
        /// </devdoc>
        public void Insert(string key, object value, CacheDependency dependencies) {
            ObjectCache.Insert(key, value, new CacheInsertOptions() { Dependencies = dependencies });
        }
 
 
        /// <devdoc>
        /// <para>Inserts an object into the System.Web.Caching.Cache that has file or key dependencies and
        ///    expires at the value set in the <paramref name="absoluteExpiration"/> parameter.</para>
        /// </devdoc>
        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 = utcAbsoluteExpiration,
                                                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
                });
        }
 
 
        /// <devdoc>
        ///    <para>Removes the specified item from the cache. </para>
        /// </devdoc>
        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;
            }
        }
    }
 
}