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).
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.