Creating a Custom Output Cache Provider in ASP.NET 4

Introduction

Output Caching is a very common and popular performance improvement technique for ASP.NET applications. In the previous versions of ASP.NET the output caching mechanism of ASP.NET was rigid in terms of cache store. For example, developers didn't have any flexibility if they wanted to store the output cache in a disk file instead of the server memory. Luckily, ASP.NET 4 provides such flexibility to developers. ASP.NET 4 follows a provider model for output caching. This means you can now define a custom cache provider and plug it in using a configuration file. This article illustrates how such a custom output cache provider that stores cached output in disk files can be developed.

Creating a Custom Output Cache Provider

In order to create a custom output cache provider you need to create a class that inherits from the OutputCacheProvider base class. You are then required to override the following methods of the OutputCacheProvider class:

  • Add()
  • Remove()
  • Set()
  • Get()

The Add() method is intended to add a new output cache entry to the cache store. The cache store is customizable and in our example we will use disk files as the cache store. Remove() method removes a cache entry upon expiration. The Set() and Get() methods assign and retrieve a cache entry respectively. Usage of these methods will be clear when you implement a custom output cache provider in the next sections.

Once a custom output cache provider is ready you need to inform ASP.NET that you wish to use it in your website. You do this by configuring a cache provider in the web.config file.

Creating a FileCacheProvider Class That Stores Output Cache in Physical Disk Files

To begin creating a custom output cache provider, create a new ASP.NET website and add a new class named FileCacheProvider to the App_Code folder. The FileCacheProvider class should inherit the OutputCacheProvider base class and override the methods as discussed earlier. The following code shows the skeleton of FileCacheProvider class.

public class FileCacheProvider : OutputCacheProvider
{
 
    public override object Add(string key, object entry, DateTime utcExpiry)
    {
      ...
    }
 
    public override void Remove(string key)
    {
      ...
    }
 
    public override object Get(string key)
    {
      ...
    }
 
    public override void Set(string key, object entry, DateTime utcExpiry)
    {
      ...
    }
}

Before we override any of the base class method let's create a couple of helper methods that will be used throughout the class. First, add a read-only property CacheLocation as shown below:

private string CacheLocation
{
    get
    {
        string strCacheLocation = ConfigurationManager.AppSettings["CacheLocation"];
        strCacheLocation = HttpContext.Current.Server.MapPath(strCacheLocation);
        return strCacheLocation + @"\";
    }
}

The CacheLocation property is supposed to return the physical path of the folder that will be acting as a cache store. To avoid hardcoding of the folder name you store it in the <appSettings> section of the web.config file. The folder path is then retrieved in the CacheLocation property using the ConfigurationManager class. This path is a virtual path (say, for example, ~/Cache) and you need to convert it into a physical path using Server.MapPath() method. A "\" character is appended to the path because a file name will be appended to this base path later.

Next, add a method - GetFullPathForKey() as shown below:

 private string GetFullPathForKey(string key)
{
 string temp = key.Replace('/', '$');
 return CacheLocation + temp;
}

In Add, Remove, Set and Get methods, ASP.NET passes the path of the .aspx file being cached as the cache item key. You cannot use this key directly as a file name because it may contain "/" character(s). To take care of such situations the GetFullPathForKey() method replaces all occurrences of the "/" character with a "$" character and appends it to the CacheLocation. This way you arrive at a valid file name that can be used to persist cached data.

NOTE: The above implementation can be further made foolproof by adding checks for other invalid characters in addition to /. For the sake of this article, however, we will use the simple implementation as shown above.

Next, add Set() method as shown below:

public override void Set(string key, object entry, DateTime utcExpiry)
{
    string filePath = GetFullPathForKey(key);
    CacheItem item = new CacheItem { Expiry = utcExpiry, Item = entry };
    FileStream fileStream = File.OpenWrite(filePath);
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(fileStream, item);
    fileStream.Close();
}

The Set() method is intended to store an item in cache store. It receives a key for the item being cached, the item to be cached and its expiry date and time. Inside, it serializes an item to be cached into a file using BinaryFormatter. Notice the use of CacheItem class. The CaheItem class is a simple serializable class intended to store the item to be cached and its expiration date. It looks like this:

[Serializable]
public class CacheItem
{
    public object Item { get; set; }
    public DateTime Expiry { get; set; }
}

Make sure to mark the class with [Serializable] attribute because you wish to store (serialize) its contents to a file using BinaryFormatter. Now add the Get() method as shown below:

public override object Get(string key)
{
    string filePath = GetFullPathForKey(key);
    if (!File.Exists(filePath))
    {
        return null;
    }
    CacheItem item = null;
    FileStream fileStream = File.OpenRead(filePath);
    BinaryFormatter formatter = new BinaryFormatter();
    item = (CacheItem)formatter.Deserialize(fileStream);
    fileStream.Close();
    if (item == null || item.Expiry <= DateTime.UtcNow)
    {
        Remove(key);
        return null;
    }
    return item.Item;
}

Get() method deserializes a previously stored CacheItem using a BinaryFormatter. It also does the job of expiring a cached item based on the expiration date and time. Next, override the Add() method as shown below:

public override object Add(string key, object entry, DateTime utcExpiry)
{
  object obj = this.Get(key); 
  if (obj != null) 
  { 
    return obj; 
  }
  else 
  { 
    this.Set(key, entry, utcExpiry); 
    return entry; 
B  }
}

Add() method works similar to Set() method with one difference. If an item is already cached, Add() method will not overwrite the entry whereas Set() method will overwrite it even if it is already present. Add() method receives a key for the item being cached, the item to be cached and its expiry date and time. Finally, the Remove() method simply deletes the physical file and is shown next:

public override void Remove(string key)
{
    string filePath = GetFullPathForKey(key);
    if (File.Exists(filePath))
    {
        File.Delete(filePath);
    }
}

Configuring Your Website to Use Custom Output Cache Provider

Now that you are ready with a custom output cache provider, let's configure the website so that ASP.NET knows that your custom provider is to be used instead of the in-built mechanism. Open web.config and add an <appSettings> section as shown below:

<appSettings>
   <add key="CacheLocation" value="~/Cache"/>
</appSettings>

Here, you added CacheLocation key to the <appSettings> section. Recollect that CacheLocation key is used in the CacheLocation property of the FileCacheProvider class. In the above example, the folder in which cache files are stored is Cache. Make sure to change the folder name as per your setup. Next, add < caching> section as follows:

<caching>
   <outputCache defaultProvider="FileCache">
      <providers>
         <add name="FileCache" type="FileCacheProvider"/>
      </providers>
   </outputCache>
</caching>

The <outputCache> section adds your custom provider using the <add> element. The type attribute should point to the fully qualified name of the custom cache provider class. If your custom cache provider class is part of a class library rather than App_Code folder you should set the type attribute like this:

 <add name="FileCache" type="MyNamespace.FileCacheProvider, MyAssembly"/>

Make sure to replace namespace and assembly name as per your setup.

If you have added multiple cache providers for the sake of testing, make sure that the defaultProvider attribute of the <outputCache> element points to the correct cache provider.

Sample Run of a Web Form

Now you are ready to use the custom cache provider in web forms. Add a new web form to the website and place a Label control on it. Enable output caching for the web form using @OutputCache directive.

<%@ OutputCache VaryByParam="None" Duration="60" %>

As you can see the cache duration is set to 60 seconds. In the Page_Load event simply display the current time value in the Label.

protected void Page_Load(object sender, EventArgs e)
{
  Label1.Text = DateTime.Now.ToShortTimeString();
}

If you run the web form you will find that its output is being cached for the specified duration (as indicated by the time value outputted in the Label). You will also find files being created in the ~/Cache folder (see below).

The ~/Cache folder
Figure 1: The ~/Cache folder

Summary

ASP.NET 4.0 allows you to customize output cache store. It follows the provider model and allows you to create a custom output cache provider. A custom output cache provider is essentially a class that derives from OutputCacheProvider base class. You need to override Add(), Remove(), Set() and Get() methods to get the page output in a required cache store. The custom output cache provider is then specified with the help of the <outputCache> configuration section. In the example discussed in this article you used physical disk files to store output cache but you could have used any other storage mechanism.



About the Author

Bipin Joshi

Bipin Joshi is a blogger and writes about apparently unrelated topics - Yoga & technology! A former Software Consultant by profession, Bipin has been programming since 1995 and has been working with the .NET framework ever since its inception. He has authored or co-authored half a dozen books and numerous articles on .NET technologies. He has also penned a few books on Yoga. He was a well known technology author, trainer and an active member of Microsoft developer community before he decided to take a backseat from the mainstream IT circle and dedicate himself completely to spiritual path. Having embraced Yoga way of life he now codes for fun and writes on his blogs. He can also be reached there.

Related Articles

Downloads

Comments

  • ASP.net 4.0 Fragment Caching using Custom Cache Provider

    Posted by Ioannis Dontas on 11/10/2012 01:07am

    Dear Sir I have implemented a disk based Custom Cache Provider which works fine except from one problem. When i use the provider in aspx Pages the generated key is of the from "a2/../../default.aspx" which is fine. When I use the same provider in User Controls the generated key is of the form "lfffffff40e80...." and this is fine till the application pool recycles. Then for the same user control a similar key is generated (but not the same) meaning that the cashed user control cannot be accessed with the new key and a new entry is generated in the Disk Cache for the same user control. That means that all cached User controls become useless when Application Pool recycles. Is this the way Custom Cache Providers work with User Controls? How can I generate the same key for the User Control when the application pool recycles? Thank you in advance!

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

Top White Papers and Webcasts

  • Wednesday, September 24, 2014 8:00 AM - 9:00 AM PDT According to a recent Forrester Research report, many companies are choosing low-code platforms over traditional programming platforms, due to the speed with which low-code apps can be assembled and tested. With customer-facing applications on the rise, traditional programming platforms simply can't keep up with the "short schedules and rapid change cycles" required to develop these applications. Check out this upcoming webinar and join Clay Richardson from …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds