Web Development & Execution
David Addison
by David Addison
share this
?fl
« Back to the Blog

Asp.NET caching 101: cache key dependencies

03/28/2011
Asp.NET caching 101: cache key dependencies

Welcome to the next installment of Caching 101. In this article we will take a look at adding cache key dependencies in Adxstudio CMS. Adxstudio is a Microsoft Gold Partner that provides CRM and CMS portal solutions. Because of its excellent reputation Dirigo has chosen it as our stock CMS system.

In the previous article Asp.NET Caching 101 we looked at basic caching output with expiration by time. In this post I am going to demonstrate how to dynamically expire cache using something called a cache dependency. There are three types of cache dependencies:

  • Cache key dependency–This is a key/value pair of type string/object.
  • Cache file dependency–The expiration depends on a reference to a file on disk. When the file is updated the cache expires. A common scenario is an XML data file that generates navigation. When the file updates your navigation will update.
  • Sql dependency–This is a sql based dependency that references sql object. When these objects are updated the cache expires.

At Dirigo we have developed a proprietary code library that interfaces with the Adxstudio CMS API to extend the functionality of an already great system. For instance on embodiworks.org the left navigation for the sub pages is driven directly from Adxstudio CMS. The underlying code for the control uses a recursive algorithm to parse the relationships of the nodes—information that the Adxstudio CMS API returns (e.g. whether a node has child nodes and whether these nodes should be displayed or whether the node in question has special display characteristics like a pop-up window attached to the click event). These special characteristics are defined by custom data files or extensions we added to the Adxstudio CMS API.

The recursive algorithms can be resource intensive and the code requires many calls to the database through the API. The solution, of course, is to cache your output. At first just generating the left navigation without caching was tolerable since it was relatively fast. However on embodiworks.org we later implemented large mega-menus in the top navigation. Each of these mega-menus use the same code the left navigation uses. So now our navigational calculations would take about four times longer than originally anticipated. In our test environment with no external server load the website takes up to 20 seconds to load. By implementing caching using cache key dependencies, the pages loaded as quickly as 0.15 seconds after the initial load.

Caching for 15 minutes seemed like a good idea but pages would still load slowly because the cache would expire every 15 minutes. It was not necessary for them to expire every 15 minutes because no content had changed. With the time duration set to 15 minutes (just in case the content changed), we wouldn’t have to wait hours for it to update.

Ideally we would want to have the menus update instantly and not have to wait for the cache to expire to see our results. The solution was to attach cache key dependencies to existing Adxstudio CMS cache objects. Adding cache key dependences, we can cache the output for as long as we want, thereby drastically improving the Web site performance and reducing server load

Getting Started

Let’s start off with a quick example of setting up a cache key dependency. Open Visual Studio 2010 and start a new Web application. In the first part of this series I provided a code snippet that showed you how to add a cache object with the key name of “test.” Here is that same snippet. Add to the Page_Load method in the codebehind of Default.aspx.cs.

if (HttpContext.Current.Cache["test"] == null)

{

HttpContext.Current.Cache.Insert(

"test",

System.Guid.NewGuid(),

null,

DateTime.Now.AddSeconds(15),

System.Web.Caching.Cache.NoSlidingExpiration);

}

Response.Write(Cache["test"]);

This creates a cache object with a key of “test.” This object will expire every 15 seconds at which point a new Guid is generated and added to the cache with the same parameters. What if we don’t want to wait 15 seconds? Create a new page in your project named CacheClearDependency.aspx and add this line to the Page_Load method:

HttpContext.Current.Cache.Remove("test");

This removes the cache object with the name “test.” Now let’s test an actual cache dependency. So far, we have created a page that adds an item to the cache, and another that removes it from the cache. Let’s make another page where cacheability depends on this cache item:

Make yet a third page named CacheDependent.aspx, add this line to the CacheDependent.aspx:

<%@ OutputCache VaryByParam="none" Location="Server" Duration="30" %>

Add these lines to the Page_Load method in the codebehind:

Response.AddCacheItemDependency("test");

Response.Write(System.Guid.NewGuid().ToString());

The CacheDependent.aspx’s cache is dependent on the state of the cache object with the key “test.” When the cache item “test” expires, or is cleared, CacheDependent.aspx’s cache is also cleared. This is handy for server controls that cache the output but depend on data and input that may change elsewhere in the application. Here are some simple test scenarios;

  1. Test basic cache key clearing:
    • Load Default.aspx, you will see a Guid value, keep refreshing the page and it will be the same value until after 15 seconds which is when the cache expires. If you keep refreshing it will be the new value until another 15 seconds passes.
    • Load CacheClearDependency.aspx and then refresh Default.aspx. You will notice that the cached output updates immediately after you visit CacheClearDependency.aspx.
  2. Test the cache key dependency:
    • Load Default.aspx, observe the Guid. Load CacheDependent.aspx, observe the Guid.
    • Wait 15 seconds while refreshing both pages. Notice that the Guids remain the same.
    • After 15 seconds refresh Default.aspx and it will have a new Guid. Next you can refresh CacheDependent.aspx and it will also have a new Guid. The key here is that CacheDependt.aspx does not clear its own cache, it is waiting for the cache expiration contained in Default.aspx.
  3. One small feature to be aware of:
    • Visit CacheClearDependency.aspx, this will clear the cache key “test.”
    • Next visit CacheDependent.aspx and refresh several times. Each time there is a new Guid, because there is no cache key “test” for this page to depend. Every time it is cached but the cache is refreshed with each load.
    • Next visit Default.aspx, this will re-add the cache key “test.”
    • Refresh CacheClearDependency.aspx several times and you will see that the output is now depending on the cache key “test” once again.

Adxstudio CMS and Cache key dependencies

If you are running Adxstudio CMS on your local workstation, or you have local access to your server, you can view all existing cache key dependencies. Make sure that you have this entry in your web.config:

<httpHandlers>

       <add verb="*" path="CacheTrace.axd"             type="Adxstudio.Web.Handlers.CacheTraceHandler"/>

</httpHandlers>

In order ot see the CacheTrace.axd output just visit: http://yourwebsite/CacheTrace.axd. Here is a sample of the CacheTrace.axd output, the “Key” column is an example of the format that Adxstudio CMS uses for cache key dependencies.

Key Type Size
adxdependency:adxtagging:tag:guid:07c2e32e-39a2-4475-a0d6-60badb96e148 System.String 0.094
adxdependency:adxtagging:tag:guid:259d8f66-6f70-4de2-abc1-25e909378f92 System.String 0.094
adxdependency:adxtagging:tag:guid:4e03f0cb-91b8-431d-a363-dcbaa890034e System.String 0.094
adxdependency:adxtagging:tag:guid:b38acbeb-8226-4fc5-be5c-762a27fcf4a9 System.String 0.094
adxdependency:adxtagging:tag:guid:dbc4d8bc-394e-420c-ac82-0bee3f73020e System.String 0.094

Because Adx has already setup the cache keys we don’t have to generate them like we did in the previous example; just add dependencies. There are many different kinds of dependencies that Adxstudio CMS uses. Let’s concentrate on dependencies for documents.

The format of the key is adxdependency:document:[insert your Guid here]. To add this dependency to a page, add this line in the Page_Load method:

Response.AddCacheItemDependency("adxdependency:document:[insert Guid here]");

There is also an overloaded method for AddCacheItemDependency that takes an array of strings in the same format:

Response.AddCacheItemDependencies(new string[] { "adxdependency:document:[insert Guid here]" });

A user control is a little different. You have to go through the CachePolicy object and it only accepts an array of strings. Here is an example:

string[] dependencies = new string[] { "adxdependency:document:[insert Guid here]" };

this.CachePolicy.Dependency = new System.Web.Caching.CacheDependency(null, dependencies);

I did not find an easy solution for implementing cache key dependencies with a server control so for now I am mostly implementing my server controls in user controls and then implementing the cache dependencies on a per Web site basis.

My navigational server controls are setup where they all stem from a base class that has a List collection that contains all of relevant documents for that area of the site. I am able to easily iterate the documents for any server control that inherits from the base class. Here is an example of how I have implemented a method called GetCacheKeyDependencies() that will return all of the cache key dependencies for a particular server control:

public string[] GetCacheKeyDependencies()

{

List depslist = new List ();

foreach (Document child in Documents)

{

if (child.DocumentKey != null)

{

depslist.Add(string.Format("adxdependency:document:{0}", child.DocumentKey.ToString()));

}

}

return depslist.ToArray();

}

Here is an example of how I have used this method, in a user control to add cache key dependencies:

// cached for 30 days

[PartialCaching(2592000, "DN", null, null, true)]

public partial class Leftnav : System.Web.UI.UserControl

{

protected void Page_Load(object sender, EventArgs e)

{

// append the adx cache key dependencies to this control's cache key collection

this.CachePolicy.Dependency = new System.Web.Caching.CacheDependency(null,

this.LeftNavSiblingNavigation.GetCacheKeyDependencies());

}

}

The above code is cached for 30 days unless one of the cache key dependencies is cleared in which case this user control will recalculate its output.

Conclusion

Going back to the example of embodiworks.org the mega-menus and left navigation work on all of the principles we just covered. They stem from a base class that has a single method called GetCacheKeyDependencies which is hooked into various user controls and pages. These pages are cached for 30 days unless an administrator updates a CMS object that affects a cache key dependency (if this happens the cache is immediately cleared and the page displays the results instantly).

Let’s be honest, this is easier said than done though. When you examine the complexity of Adxstudio CMS, after the first page load there are over 300 cache key dependencies (depending on the page you load). Embodiworks.org uses one left nav, four large drop downs, and 50 (give or take) cache key dependencies per page. The menus are also cached on a per-page basis because the links change and highlight depending on the page you are on. Considering there are over 200 pages on the site that is a heck of a lot of internal cache key dependency relationships.

Thanks!

Thank you for contacting us!

We'll be in touch!

Back Home ×