The Cache API


Underpinning many of the caching features described in this chapter is the Cache API; it's the base set of caching classes that ASP.NET uses, which you can also use in your code. You'll find some of the concepts and patterns used here familiar, and some you've already seen in action, such as Listing 6.17. The Cache API revolves around two main objects, both from the System.Web.Caching namespace: Cache and CacheDependency.

At its simplest, the Cache can be used like other collection-based objects, because it has a default collection for its Items property, allowing items to be inserted and extracted by indexing into the collection. For example:

Cache["Shippers"] = Shippers.GetItems(); List<Shipper> = (List<Shipper>)Cache["Shippers"];


When adding items to the cache, you can also use the Insert method, which takes two parameters, the key and the object to cache. For example:

Cache.Insert("Shippers", Shippers.GetItems());


There is a Get method to fetch items, and a Remove method to delete items from the cache.

In use, the cache can follow the familiar pattern, and to see this in action, consider the ShippersBusinessLayer created in Chapter 5, which uses the GetItemsInternal method shown in Listing 6.19.

Listing 6.19. GetItemsInternal before Caching

private static List<Shipper> GetItemsInternal() {   List<Shipper> shippers = new List<Shipper>();   DataTable tbl = ShipperDataLayer.GetShippers();   foreach (DataRow row in tbl.Rows)     shippers.Add(new Shipper((int)row["ShipperID"],       row["CompanyName"].ToString(),       row["Phone"].ToString()));   return shippers; }

Adding the basic level of caching into this would lead to Listing 6.20.

Listing 6.20. GetItemsInternal with Basic Caching

private static List<Shipper> GetItemsInternal() {   List<Shipper> shippers =       List<Shipper>)HttpContext.Current.Cache["Shippers"]; ;   DataTable tbl = ShipperDataLayer.GetShippers();   if (shippers == null)   {     foreach (DataRow row in tbl.Rows)       shippers.Add(new Shipper((int)row["ShipperID"],         row["CompanyName"].ToString(),         row["Phone"].ToString()));     HttpContext.Current.Cache["Shippers"] = shippers;   }   return shippers; }

Now the cache is checked for the shippers, and they are only fetched from the data layer if not in the cache.

Getting Items in the Cache to Expire

Items can be inserted into the cache and instructed to expire after a date or time has been reached. The date or time can be fixed, in which case the item is removed from the cache when the date and time arrives, or can be sliding, in which case the item is only removed from the cache if it hasn't been used for a given period of time. Both of these can be achieved using the Insert method, with the overloaded form having the following syntax:

Insert(Key, Item, Dependency, DateTime, TimeSpan)


The first two parameters are the same as shown previouslythe cache key and the item to cache. The Dependency is a CacheDependency object, and we'll be coming to these later, but this can be null if no external dependency is required. The DateTime is the explicit date and time the cache entry is to expire, and TimeSpan is the date and time the cache entry is to expire, calculated from the last access of the item in the cache. These are mutually exclusive, so you can set one or the other to an enumeration to indicate which action is required. For example, to have absolute expiration, you could use the following:

Cache.Insert("Shippers", ships, null,   DateTime.Now.AddSeconds(10),   Cache.NoSlidingExpiration);


Here an explicit expiration point has been set at 10 seconds after the item was added to the cache; using Cache.NoSlidingExpiration means that the expiration time is absolute, irrespective of whether the item is being fetched from the cache frequently. Alternatively, you can specify a sliding expiration:

Cache.Insert("Shippers", ships, null,   Cache.NoAbsoluteExpiration,   TimeSpan.FromSeconds(10));


Here the absolute time is set to Cache.NoAbsoluteExpiration, to indicate a sliding expiration scheme is in use. The time span is set to 10 seconds from the last time the item was read from the cache. The advantage of sliding expiration is that it makes the most of caching, keeping items in the cache if they are frequently used, but evicting them if they are not.

The cache uses system memory, so there is always a trade-off in the performance it brings, and if you need to cache several items, you can specify additional parameters to indicate the priority of the cached item in relation to other cached items. For example:

Cache.Insert("Shippers", ships, null,   Cache.NoAbsoluteExpiration,   TimeSpan.FromSeconds(10),   CacheItemPriority.AboveNormal, null);


Here an additional parameter indicates the priority of the item in the cache, and the value can be one of:

  • AboveNormal, indicating items are less likely to be evicted than items with a Normal priority

  • BelowNormal, indicating items are more likely to be evicted than items with a Normal priority

  • Default, indicating the default priority for a cached item

  • High, indicating items are least likely to be evicted

  • Low, indicating items are most likely to be evicted

  • Normal, indicating items are likely to be evicted after those with a Low or BelowNormal priority

  • NotRemovable, indicating items will never be evicted from the cache

If you wish to use priorities, but don't wish to specify dates and times for eviction, then both the DateTime and TimeSpan parameters can be set to their respective enumeration values:

Cache.Insert("Shippers", ships, null,   Cache.NoAbsoluteExpiration,   Cache.NoSlidingExpiration,   CacheItemPriority.AboveNormal, null);


The final parameter, which is null in the above code, is for a callback, allowing your code to be notified when an item is removed from the cache, which we'll look at later.

Making Cache Entries Depend upon External Factors

One problem with the code in Listing 6.20 is that since the items are cached, any changes to the underlying data won't get reflected in the data returned from the business layera problem you saw cured with the SQL Cache Dependencies object earlier in the chapter (Listing 6.16). This also affects other types of caching, where data comes from sources other than a database. XML files, for example, could be cached, but you'd want the item evicted from the cache when the file changes. Also, you may only want to cache items for a selected period of time, perhaps only while the item is being used, so that if it doesn't get used within a certain period of time it is evicted from the cache.

This is where cache dependencies come in, because you can make items in the cache dependent upon external factors such as files, times, or other items in the cache. For a cached item to be dependent upon something, you must use a CacheDependency object, which has a large number of overloaded constructors. For example, to have a file-based dependency, you could use the code shown in Listing 6.21 to fetch an XML file.

Listing 6.21. File-Based Cache Dependency

public static DataSet GetXMLShippers() {   DataSet ships = (DataSet)HttpContext.Current.Cache["shippersXML"];   if (ships == null)   {     string fileName =            HttpContext.Current.Server.MapPath("shippers.xml");     ships = new DataSet();     ships.ReadXml(fileName);     // create the dependency     CacheDependency dep = new CacheDependency(fileName);     HttpContext.Current.Cache.Insert("shippersXML", ships, dep);   }   return ships; }

This uses the same Insert method to add an item to the cache but uses an overloaded form of Insert to pass a third parameterthe dependency. This is a CacheDependency object, created by passing in the filename that the item depends upon. When the item is inserted into the cache, the dependency keeps track of the file, and if the file changes, the item is evicted from the cache.

The CacheDependency object has a number of overloaded constructors, taking a variety of file names, dates, and other cache keys. For example, to have a dependency upon a file, you simply pass in the filename, or an array of filenames, for dependency upon more than one file):

CacheDependency dep = new CacheDependency(fileName);


Another form takes two string arrays; the first is an array of filenames, the second is an array of cache keys, so you can have a dependency upon multiple files, or other entries in the cache, thus chaining dependencies.

Multiple Cache Dependencies

Since the CacheDependency object has so many overloads for its constructor, a neater way to have multiple dependencies is to use the AggregateCacheDependency object, which allows you to package up the dependencies into a single aggregated object and then have the dependency based upon that. This aggregated class inherits from CacheDependency, so it just provides a way to have a single dependency to deal with and doesn't offer any features that CacheDependency doesn't have.

Being Notified When an Item in the Cache Expires

Removal notification allows you to identify when, and why, an item was removed from the cache, allowing you to perform a custom action, such as reloading the cached data if required. Within ASP.NET pages, this item isn't particularly useful, but it comes into its own when dealing with global data. For example, consider a Web site that displays a funny quote on each page, or a word of the day, the data of which is a sensible candidate for caching. You could load and cache this as the application starts, with a dependency, but how would you reload the data if it changed? The answer is a removal callback. For example, consider Listing 6.22, which depicts a simple static class to load word definitions into a string collection and to add new definitions to the file.

This could be loaded into the cache as the application starts, as shown in Listing 6.23. In the Application_Start event, the Words class is used to return the words, which are inserted into the cache with a dependency upon the file. The addition over earlier examples is the use of CacheItemRemovedCallback, which defines the method to call when the item is removed from the cache. So, if the words file is updated, the string collection will be removed from the cache, and RefreshWords will be called.

Listing 6.22. The Words Class

public static class Words {   public static string WordsFile;   static Words()   {     WordsFile =       HttpContext.Current.Request.ServerVariables[       "APPL_PHYSICAL_PATH"]       + @"ch06\WordDefinitions.txt";   }   public static StringCollection ReadWords()   {     StringCollection words = new StringCollection();     using (StreamReader rdr = new StreamReader(WordsFile))     {       string line;       while ((line = rdr.ReadLine()) != null)         words.Add(line);       rdr.Close();     }     return words;   }   public static void Add(string word, string definition)   {     using (StreamWriter wrt = new StreamWriter(WordsFile, true))     {       wrt.WriteLine(word + ":" + definition);       wrt.Close();     }   } }

The signature of the callback, RefreshWords, must have the three parameters as defined here: the key of the item removed, the object removed, and the reason (which will be one of DependencyChanged, Expired, Removed, or Underused). Within RefreshWords the data is simply reloaded into the cache.



ASP. NET 2.0 Illustrated
ASP.NET 2.0 Illustrated
ISBN: 0321418344
EAN: 2147483647
Year: 2006
Pages: 147

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net