// // System.Web.Caching // // Author: // Patrik Torstensson // Daniel Cazzulino [DHC] (dcazzulino@users.sf.net) // // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Threading; namespace System.Web.Caching { public sealed class Cache : IEnumerable { public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue; public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero; // Helper objects private CacheExpires _objExpires; // The data storage private Hashtable _arrEntries; private ReaderWriterLock _lockEntries; private int _nItems; static private TimeSpan _datetimeOneYear = TimeSpan.FromDays (365); public Cache () { _nItems = 0; _lockEntries = new ReaderWriterLock (); _arrEntries = new Hashtable (); _objExpires = new CacheExpires (this); } /// /// Internal method to create a enumerator and over all public /// items in the cache and is used by GetEnumerator method. /// /// /// Returns IDictionaryEnumerator that contains all public items in the cache /// private IDictionaryEnumerator CreateEnumerator () { Hashtable objTable; //Locking with -1 provides a non-expiring lock. _lockEntries.AcquireReaderLock (-1); try { // Create a new hashtable to return as collection of public items objTable = new Hashtable (_arrEntries.Count); foreach (DictionaryEntry objEntry in _arrEntries) { if (objEntry.Key == null) continue; CacheEntry entry = (CacheEntry) objEntry.Value; if (entry.IsPublic) objTable.Add (objEntry.Key, entry.Item); } } finally { _lockEntries.ReleaseReaderLock (); } return objTable.GetEnumerator (); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } public IDictionaryEnumerator GetEnumerator () { return CreateEnumerator (); } internal void Touch(string strKey) { GetEntry (strKey); } /// /// Adds the specified item to the Cache object with /// dependencies, expiration and priority policies, and a /// delegate you can use to notify your application when the /// inserted item is removed from the Cache. /// /// The cache key used to reference the item. /// The item to be added to the cache. /// /// The file or cache key dependencies for the item. When any /// dependency changes, the object becomes invalid and is removed /// from the cache. If there are no dependencies, this paramter /// contains a null reference. /// /// /// The time at which the added object expires and is removed from the cache. /// /// /// The interval between the time the added object was last /// accessed and when that object expires. If this value is the /// equivalent of 20 minutes, the object expires and is removed /// from the cache 20 minutes after it is last accessed. /// /// /// The relative cost of the object, as expressed by the /// CacheItemPriority enumeration. The cache uses this value when /// it evicts objects; objects with a lower cost are removed from /// the cache before objects with a higher cost. /// /// /// A delegate that, if provided, is called when an object is /// removed from the cache. You can use this to notify /// applications when their objects are deleted from the /// cache. /// /// The Object item added to the Cache. public object Add (string strKey, object objItem, CacheDependency objDependency, DateTime absolutExpiration, TimeSpan slidingExpiration, CacheItemPriority enumPriority, CacheItemRemovedCallback eventRemoveCallback) { return Add (strKey, objItem, objDependency, absolutExpiration, slidingExpiration, enumPriority, eventRemoveCallback, true, false); } private object Add (string strKey, object objItem, CacheDependency objDependency, DateTime absolutExpiration, TimeSpan slidingExpiration, CacheItemPriority enumPriority, CacheItemRemovedCallback eventRemoveCallback, bool pub, bool overwrite) { if (strKey == null) throw new ArgumentNullException ("strKey"); if (objItem == null) throw new ArgumentNullException ("objItem"); if (slidingExpiration > _datetimeOneYear) throw new ArgumentOutOfRangeException ("slidingExpiration"); CacheEntry objEntry; CacheEntry objOldEntry = null; long longHitRange = 10000; // todo: check decay and make up the minHit range objEntry = new CacheEntry (this, strKey, objItem, objDependency, eventRemoveCallback, absolutExpiration, slidingExpiration, longHitRange, pub, enumPriority); Interlocked.Increment (ref _nItems); _lockEntries.AcquireWriterLock (-1); try { if (_arrEntries.Contains (strKey)) { if (overwrite) objOldEntry = _arrEntries [strKey] as CacheEntry; else return null; } objEntry.Hit (); _arrEntries [strKey] = objEntry; } finally { _lockEntries.ReleaseLock (); } if (objOldEntry != null) { if (objOldEntry.HasAbsoluteExpiration || objOldEntry.HasSlidingExpiration) _objExpires.Remove (objOldEntry); objOldEntry.Close (CacheItemRemovedReason.Removed); } // If we have any kind of expiration add into the CacheExpires class if (objEntry.HasSlidingExpiration || objEntry.HasAbsoluteExpiration) { if (objEntry.HasSlidingExpiration) objEntry.Expires = DateTime.UtcNow.Ticks + objEntry.SlidingExpiration; _objExpires.Add (objEntry); } return objEntry.Item; } /// /// Inserts an item into the Cache object with a cache key to /// reference its location and using default values provided by /// the CacheItemPriority enumeration. /// /// The cache key used to reference the item. /// The item to be added to the cache. public void Insert (string strKey, object objItem) { Add (strKey, objItem, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Default, null, true, true); } /// /// Inserts an object into the Cache that has file or key dependencies. /// /// The cache key used to reference the item. /// The item to be added to the cache. /// /// The file or cache key dependencies for the item. When any /// dependency changes, the object becomes invalid and is removed /// from the cache. If there are no dependencies, this paramter /// contains a null reference. /// public void Insert (string strKey, object objItem, CacheDependency objDependency) { Add (strKey, objItem, objDependency, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Default, null, true, true); } /// /// Inserts an object into the Cache with dependencies and expiration policies. /// /// The cache key used to reference the item. /// The item to be added to the cache. /// /// The file or cache key dependencies for the item. When any /// dependency changes, the object becomes invalid and is removed /// from the cache. If there are no dependencies, this paramter /// contains a null reference. /// /// /// The time at which the added object expires and is removed from the cache. /// /// The interval between the /// time the added object was last accessed and when that object /// expires. If this value is the equivalent of 20 minutes, the /// object expires and is removed from the cache 20 minutes after /// it is last accessed. /// public void Insert (string strKey, object objItem, CacheDependency objDependency, DateTime absolutExpiration, TimeSpan slidingExpiration) { Add (strKey, objItem, objDependency, absolutExpiration, slidingExpiration, CacheItemPriority.Default, null, true, true); } /// /// Inserts an object into the Cache object with dependencies, /// expiration and priority policies, and a delegate you can use /// to notify your application when the inserted item is removed /// from the Cache. /// /// The cache key used to reference the item. /// The item to be added to the cache. /// /// The file or cache key dependencies for the item. When any /// dependency changes, the object becomes invalid and is removed /// from the cache. If there are no dependencies, this paramter /// contains a null reference. /// /// /// The time at which the added object expires and is removed from the cache. /// /// /// The interval between the time the added object was last /// accessed and when that object expires. If this value is the /// equivalent of 20 minutes, the object expires and is removed /// from the cache 20 minutes after it is last accessed. /// /// The relative cost of the object, /// as expressed by the CacheItemPriority enumeration. The cache /// uses this value when it evicts objects; objects with a lower /// cost are removed from the cache before objects with a higher /// cost. /// /// A delegate that, if /// provided, is called when an object is removed from the cache. /// You can use this to notify applications when their objects /// are deleted from the cache. /// public void Insert (string strKey, object objItem, CacheDependency objDependency, DateTime absolutExpiration, TimeSpan slidingExpiration, CacheItemPriority enumPriority, CacheItemRemovedCallback eventRemoveCallback) { Add (strKey, objItem, objDependency, absolutExpiration, slidingExpiration, enumPriority, eventRemoveCallback, true, true); } // Called from other internal System.Web methods to add non-public objects into // cache, like output cache etc internal void InsertPrivate (string strKey, object objItem, CacheDependency objDependency, DateTime absolutExpiration, TimeSpan slidingExpiration, CacheItemPriority enumPriority, CacheItemRemovedCallback eventRemoveCallback) { Add (strKey, objItem, objDependency, absolutExpiration, slidingExpiration, enumPriority, eventRemoveCallback, false, true); } /// /// Removes the specified item from the Cache object. /// /// The cache key for the cache item to remove. /// /// The item removed from the Cache. If the value in the key /// parameter is not found, returns a null reference. /// public object Remove (string strKey) { return Remove (strKey, CacheItemRemovedReason.Removed); } /// /// Internal method that updates the cache, decremenents the /// number of existing items and call close on the cache entry. /// This method is also used from the ExpiresBuckets class to /// remove an item during GC flush. /// /// The cache key for the cache item to remove. /// Reason why the item is removed. /// /// The item removed from the Cache. If the value in the key /// parameter is not found, returns a null reference. /// internal object Remove (string strKey, CacheItemRemovedReason enumReason) { CacheEntry objEntry = null; if (strKey == null) throw new ArgumentNullException ("strKey"); _lockEntries.AcquireWriterLock (-1); try { objEntry = _arrEntries [strKey] as CacheEntry; if (null == objEntry) return null; _arrEntries.Remove (strKey); } finally { _lockEntries.ReleaseWriterLock (); } if (objEntry.HasAbsoluteExpiration || objEntry.HasSlidingExpiration) _objExpires.Remove (objEntry); objEntry.Close (enumReason); Interlocked.Decrement (ref _nItems); return objEntry.Item; } /// /// Retrieves the specified item from the Cache object. /// /// The identifier for the cache item to retrieve. /// The retrieved cache item, or a null reference. public object Get (string strKey) { CacheEntry objEntry = GetEntry (strKey); if (objEntry == null) return null; return objEntry.Item; } internal CacheEntry GetEntry (string strKey) { CacheEntry objEntry = null; long ticksNow = DateTime.UtcNow.Ticks; if (strKey == null) throw new ArgumentNullException ("strKey"); _lockEntries.AcquireReaderLock (-1); try { objEntry = _arrEntries [strKey] as CacheEntry; if (null == objEntry) return null; } finally { _lockEntries.ReleaseReaderLock (); } if (objEntry.HasSlidingExpiration || objEntry.HasAbsoluteExpiration) { if (objEntry.Expires < ticksNow) { Remove (strKey, CacheItemRemovedReason.Expired); return null; } } objEntry.Hit (); if (objEntry.HasSlidingExpiration) { long ticksExpires = ticksNow + objEntry.SlidingExpiration; _objExpires.Update (objEntry, ticksExpires); objEntry.Expires = ticksExpires; } return objEntry; } /// /// Gets the number of items stored in the cache. /// public int Count { get { return _nItems; } } /// /// Gets or sets the cache item at the specified key. /// public object this [string strKey] { get { return Get (strKey); } set { Insert (strKey, value); } } } }