Improving Your Web App's Performance with Aggressive Data Caching

Anyone who has worked on Web applications that use dynamic content knows that data access can be a real bottleneck. One of the nice features in the ASP.NET runtime is its data-caching services via the System.Web.Caching namespace. The challenge most developers face is how to plan ahead for data caching and how to effectively implement it in a way that doesn't over-complicate the code. This article offers a quick review of the features and functions of ASP.NET's data caching. You'll also learn how to build a very simple data-caching "plug-in" assembly in less than 100 lines of code. Finally, you'll see how you can easily use this assembly in your ASP.NET pages to help boost the performance of your Web apps without adding a lot of lines of code.

The Basics of ASP.NET's Data Caching

ASP.NET's data caching provides an in-memory, application-scoped, thread-safe "bucket" that can hold any serializable object or object collection. You can use this feature to hold the results of expensive database queries, large XML documents, and even simple arrays and custom objects you design yourself. Then, during the lifetime of your Web application, you can recall this data from the cache instead of having to go back to the original source every time. Even better, the data cache has features that allow you to set the lifetime of an item in the cache. This enables you to, in some cases, automatically refresh the cache when the underlying data changes.

So, the challenge is to turn this high-powered object into an easy-to-use tool in your Web application toolbox. What you need is a single class that encapsulates the most commonly used features of ASP.NET's data caching and one that provides shortcuts for inserting, refreshing, and recalling data from the cache.

Designing the Cache Utility Class

To make it easier to handle cached items, you can use a simple class to encapsulate key functions and simplify reading, writing, listing, and removing items in the cache. Below is a list of the methods and the associated arguments for each method:

public static string ListCache()
public static string ListCache(string mask)

public static void ClearCache()
public static void ClearCache(string mask)

public static object GetCacheItem(string key)

public static void DropCacheItem(string key)

public static void SetCacheItem(string key, object data)
public static void SetCacheItem(string key, object data,
   CacheDependency dependencies)
public static void SetCacheItem(string key, object data,
   int seconds)
public static void SetCacheItem(string key, object data,
   int seconds, int slidingSeconds)

GetCacheItem and DropCacheItem are very simple—they return or remove a selected item. ListCache will return a list of the items in the cache. One version of the method allows you to pass a filter string to control which items are returned. ClearCache will remove either all the items in the cache or just the ones that match the filter string.

The really interesting method is SetCacheItem. It allows you to add an item to the cache and optionally set either a dependency (usually a file name) or a set of expiration values (the maximum life of the object in seconds and/or the maximum idle time of the object in seconds). Dependencies work like this: You can link a cached item to an external item such as a disk file. If that disk file changes, the item is automatically removed from the cache. This is how ASP.NET tracks changes in your WEB.CONFIG files.

Expiration policies are a bit trickier. You can set the lifetime of an object (in this class in seconds). If the object has been in the cache for more than the lifetime, it is automatically removed. You also can set the idle-time of the object (in this class in seconds). If an object has been left idle, meaning no one has requested it, for more than the indicated seconds, it is removed from the cache. The following is the coding for the SetCacheItem method that deals with lifetime and idle time:

public static void SetCacheItem(string key, object data,
   int seconds, int slidingSeconds)
{
   if(slidingSeconds>0)
      HttpContext.Current.Cache.Insert(key,data,null,
         System.DateTime.MaxValue,TimeSpan
               .FromSeconds(slidingSeconds));
   else
      HttpContext.Current.Cache.Insert(key,data,null,
         System.DateTime.Now.AddSeconds(seconds),
         TimeSpan.FromSeconds(slidingSeconds));
}

The ListCache method returns a simple list (in HTML format) of all the items in the cache. The only challenge to listing items from the cache is that "walking" the Cache collection requires the use of an Enumerator.

public static string ListCache(string filter)
{
   StringBuilder sb = new StringBuilder();
   string key="";

   System.Collections.IDictionaryEnumerator en = 
      HttpContext.Current.Cache.GetEnumerator();
   while(en.MoveNext())
   {
      key=en.Key.ToString();
      if(filter.Length!=0 && key.IndexOf(filter)!=-1)
         sb.AppendFormat("{0}",key);
      if(filter.Length==0)
         sb.AppendFormat("{0}",key);
   }
   return sb.ToString();
}

This summarizes the basic class that provides easy access to the ASP.NET Cache class. The next step is to create a helper class that uses the basic methods to provide the needed high-level access to the cache collection.

Coding the Cache Object Class

The Cache Utility is nice, but it doesn't really make using the ASP.NET Cache class in a real Web application much easier. For that, you need one more class—a helper class—that provides caching for commonly used data objects. For example, a method that makes it easy to handle a cached DataSet or an XML document. The following code handles a cached DataSet object:

public DataSet GetSqlDataSet(string key, string connection,
   string query, bool refresh, int ttlSeconds, int idleSeconds)
{
   DataSet ds = null;

   try
   {
      if(refresh==false)
         ds = (DataSet)CacheUtility.GetCacheItem(key);
   }
   catch {}

   if(ds == null)
   {
      try
      {
         ds = new DataSet();
         SqlDataAdapter da = new SqlDataAdapter(query,connection);
         da.Fill(ds);
      }
      catch (Exception ex)
      {
         throw new ApplicationException("GetSqlDataSet failed.",ex);
      }

   if(idleSeconds>0 && ttlSeconds>0)
      CacheUtility.SetCacheItem(key,ds,ttlSeconds,idleSeconds);
   if(idleSeconds<1 && ttlSeconds>0)
      CacheUtility.SetCacheItem(key,ds,ttlSeconds);
   else
      CacheUtility.SetCacheItem(key,ds);
   }
   return ds;
   }
}

As you can see, this method allows you to pass the information needed to retrieve the DataSet from the cache collection (key), as well as the details needed to create the DataSet (query and connection) and store it in the cache (ttlSeconds and slidingSeconds). You'll even find an argument that allows you to "force" a refresh of the cache (refresh). The first time you use this method in your code, it will find no item in the cache, execute the database query to populate the DataSet, and then place that DataSet into the cache for later use. All subsequent calls will return the item from the cache until the lifetime or idle time is passed. At that time, the data will be retrieved from the database and again placed in the cache.

The code example that accompanies this article also includes other helper methods with optional argument lists to handle various options. The following is a list of those methods:

public DataSet GetSqlDataSet(string key, string connection,
   string query)
public DataSet GetSqlDataSet(string key, string connection,
   string query, bool refresh)
public DataSet GetSqlDataSet(string key, string connection,
   string query, bool refresh, int ttlSeconds)
public DataSet GetSqlDataSet(string key, string connection,
   string query, bool refresh, int ttlSeconds, int idleSeconds)

public DataSet GetXmlDataSet(string key, string filename)
public DataSet GetXmlDataSet(string key, string filename,
   bool refresh)
public DataSet GetXmlDataSet(string key, string filename,
   bool refresh, bool depends)

public XmlDocument GetXmlDocument(string key, string url)
public XmlDocument GetXmlDocument(string key, string url,
   bool refresh)
public XmlDocument GetXmlDocument(string key, string url,
   bool refresh, int ttlSeconds)
public XmlDocument GetXmlDocument(string key, string url,
   bool refresh, int ttlSeconds, int idleSeconds)

public XslTransform GetXslTransform(string key, string url)
public XslTransform GetXslTransform(string key, string url,
   bool refresh)
public XslTransform GetXslTransform(string key, string url,
   bool refresh, int ttlSeconds)
public XslTransform GetXslTransform(string key, string url,
   bool refresh, int ttlSeconds, int idleSeconds)

Now, all that's left is to test this class library in an ASP.NET Web page.

Testing the Cache Library

A simple way to test the library is to build an ASP.NET Web page that displays the results of a DataSet. The following code shows how you can handle a button click event that calls for the display of data from the pubs database in a data bound grid:

void btnGetData_OnClick(object sender, EventArgs args)
{
   CacheObjects co = new CacheObjects();
   bool refresh = cbxRefresh.Checked;
   int ttlSeconds = Int32.Parse(txtTTLSeconds.Text);
   int slidingSeconds = Int32.Parse(txtSlidingSeconds.Text);
   DataSet ds = co.GetSqlDataSet(key,connection,query,
      refresh,ttlSeconds,slidingSeconds);
   dgAuthors.DataSource = ds;
   dgAuthors.DataBind();
}

As you can see, using this cached version of the DataSet object requires very little extra code. Figure 1 shows the resulting Web page.

Figure 1: ASP.NET Web Page Displaying DataSet Results

The code download that accompanies this article also includes examples of handling XML documents, custom objects, and even simple ArrayList collections using the Cache Library.

What Have You Learned?

In this article, you learned how to use the ASP.NET Caching class to create a simple utility that enables you to cache data from various sources (database, disk files, and so forth) and control their lifetime in the cache. You also saw examples of how to write helper methods that make access to both the data and the cache quick and easy. Finally, you saw an example of how to use these helper classes within an ASP.NET Web page without much extra coding.

The length of this article does not allow for thorough coverage of the System.Web.Caching namespace. Also, the helper class examples in the code cover just a few of the possibilities. By digging into the documentation for the Caching namespace and doing a bit of experimenting on your own, you should be able to expand this simple code example into a powerful utility that will add performance and flexibility to your ASP.NET solutions.



About the Author

Mike Amundsen

An internationally known author and lecturer, Mike Amundsen travels throughout the United States and Europe speaking and teaching on a wide range of software-related topics. He has more than a dozen proramming books to his credit. When he is not working, Mike spends time with his wife and three children at their home in Kentucky, USA.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • This paper introduces IBM Java on the IBM PowerLinux 7R2 server and describes IBM's implementation of the Java platform, which includes IBM's Java Virtual Machine and development toolkit.

  • Get Gartner's NEW Magic Quadrant for Solid-State Arrays. Selecting new storage or just researching? Simplify your vendor evaluation with Gartner's 2014 Magic Quadrant for Solid-State Arrays. This report covers: Strengths & cautions for 12 vendors Assessment of each vendor's completeness of vision and ability to execute Key criteria for evaluating Solid-State Array vendors

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds