Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Cache Management in ASP.NET

4.65/5 (76 votes)
28 Oct 200515 min read 2  
This article describes how cache management is done in ASP.NET and what are the different options available in ASP.NET.

Introduction

Before explaining cache management in ASP.NET, let me clarify that different people use different terms for explaining the same concept, i.e., managing data. Some people refer to it as state management and some others refer to it as cache management. I love to use the term cache management, may be because I like the word "cache". But conceptually, there is no difference between these two. Now let's discuss different aspects of cache management (or state management) in ASP.NET.

Although cache management is not an issue in Windows applications, it has always been a challenge in the web environment. Since HTTP is a stateless protocol and a web server doesn't recognize users between different requests, it becomes very important for us to recognize a particular user between different requests and also store data so that it can be re-used between different requests. ASP.NET provides many features for storing data both in the client (browser) and the server (web server) sides, but sometimes we get confused with when to use what. In ASP.NET, we come across features like Session, Application and Cache objects, but it is important for us to understand the difference between them in order to effectively use them in web applications.

Background

In this article, I will touch upon the different cache management options available in ASP.NET. In a web application, sometimes we require to store data in the server side to avoid costly data retrieval operation from data stores and time consuming data formatting logic to improve application performance as well as to re-use the same data in subsequent requests across users, applications and machines. So, to achieve this, we need to store (cache) data in the server side.

Caching helps us to achieve three important aspects of QoS (Quality Of Service):

  • Performance - Caching improves application performance by minimizing data retrieval and formatting operations.
  • Scalability - Since caching minimizes data retrieval and formatting operations, it reduces the load on server resources thus increasing the scalability of the application.
  • Availability - Since the application uses data from a cache, the application will survive failures in other systems and databases.

Different Options

In a web application, we can cache data, pages, etc. both in server and client sides. Let us have a look at the different options available in ASP.NET for caching data both in server and client sides.

Server Side Cache Management

ASP.NET Session State

ASP.NET session state is used to cache data per user session. It means that the data cannot be shared across multiple users and the data usage is limited to the user session it is created in. ASP.NET Session object is used for this purpose.

ASP.NET session state can be managed in three different ways:

  • InProc - Stored in the aspnet_wp.exe process area. The session data is lost when the process or the application domain is recycled.
  • StateServer - Session state is stored in a separate process (aspnet_state.exe) which can be stored in a different machine. Since it can be stored in a different machine, this option will work in a web farm scenario.
  • SQLServer - Session state is stored in a SQL Server database. This option will also work in a web farm scenario.

Both in StateServer and SQLServer options, we need to ensure that the objects we cache are serializable as data storages are out-of-process systems. Both these options have impact on the application performance as data retrieval and saving operations take more time when compared to the InProc option. So based on our application requirement, we should choose the option that best suits our requirement.

The following example shows how the Session object can be used in the code:

C#
string empNum = Request.QueryString["empnum"];
if (empNum != null)
{
    string details = null;

    if (Session["EMP_DETAILS"] == null)
    {
        //Get Employee Details for employee number passed
        string details = GetEmployeeDetails(Convert.ToInt32(empNum));

        Session["EMP_DETAILS"] = details;
    }
    else
    {
        details = Session["EMP_DETAILS"];
    }

    //send it to the browser
    Response.Write(details);
}

ASP.NET Application Object

ASP.NET provides an object called Application object to store data that is accessible to all user requests. The life span of this object is tied to the application and it is re-created every time the application starts. Unlike ASP.NET Session object, this object is accessible to all user requests. Since this storage is created and maintained in an application domain space, this should not be used for data storage in a web farm scenario. This option is very useful to store data like the application metadata (CONFIG files data) that can be loaded to the Application object during application start up and can be used during the life of the application without reloading it every time for each user request. But if there is a requirement to invalidate the cached data whenever there is any change to the CONFIG files while the application is running, this option should not be used as it doesn't provide any feature to expire the cached data. So in this case, other options like the ASP.NET Cache object should be used, which is explained below.

ASP.NET Cache Object

ASP.NET cache object is my favorite caching mechanism. That's why I love to talk more about it. ASP.NET provides a key-value pair object - the Cache object which is available in the System.Web.Caching namespace. The scope of it is the application domain and the life span is tied to the application. Unlike the ASP.NET Session object, this object is accessible to all user requests.

Although both Application and Cache objects look the same, the key difference between them is the added features provided by the Cache object like the expiration policies and dependencies. It means that the data stored in the cache object can be expired/removed based on some predefined time limit set by the application code or when the dependent entity gets changed whereas this feature is not available in the Application object.

Let us discuss the different expiration policies and the dependencies that are supported.

Dependency

Dependency means that an item can be removed from the cache when a dependent entity gets changed. So a dependent relationship can be defined on an item whose removal from the cache will depend on the dependent. There are two types of dependencies supported in ASP.NET.

  • File dependency - This provides an option to remove an item automatically from the cache whenever a disk file changes. Let's say in my application, I am using an XML file to store error details (error number and error message mapping) which is used to retrieve an error message for a given error number at runtime. So instead of reading the file from the disk each time I need an error message, let's say I decide to load it once at application startup and store it in the ASP.NET cache for further use. So, if I need to change the CONFIG file to add new error messages or change some of the existing error messages while the application is running, then what will happen to my cached data? Do I need to stop the application and start it again to reflect the file changes in the application? The answer is no. The cache data can be invalidated whenever the file changes by using the File dependency option.

The following example shows how File dependency can be used to invalidate the cache item. So whenever the errors.xml file changes, the cache item will automatically get expired and will be removed from the cache.

C#
object errorData;
//Load errorData from errors.xml
CacheDependency fileDependency =
    new CacheDependency(Server.MapPath("errors.xml"));
Cache.Insert("ERROR_INFO", errorData, fileDependency);
  • Key dependency - Key dependency is similar to file dependency but the only difference is that instead of a file, the item is dependent on another item in the cache and gets invalidated when the dependent item gets changed or removed. This option is useful when multiple related items are added to the cache and those items are to be removed if the primary item is changed. For e.g., employee number, name, address and salary are added to the cache and if the employee number is updated/removed, all the related employee information are removed. In this case, the employee number dependency can be used for other employee information.

The following example shows how Key dependency can be used to invalidate the cache items.

C#
string[] relatedKeys = new string[1]; 
relatedKeys[0] = "EMP_NUM";
CacheDependency keyDependency = new CacheDependency(null, relatedKeys); 
Cache["EMP_NUM"] = 5435;
Cache.Insert("EMP_NAME", "Shubhabrata", keyDependency);
Cache.Insert("EMP_ADDR", "Bhubaneswar", keyDependency);
Cache.Insert("EMP_SAL", "5555USD", keyDependency);
Expiration Policy

Expiration policy sets the policy for how and when an item in the cache should expire.

  • Time based expiration - Time based expiration provides an option to expire an item in the cache at a predefined time. The expiration time can be set as absolute time like 31st October 2005 12:00 or sliding time, i.e., relative to the current time when the item is accessed.
C#
//Absolute Expiration
Cache.Insert("EMP_NAME", "Shubhabrata", null, 
             DateTime.Now.AddDays(1), Cache.NoSlidingExpiration);

//Sliding Expiration
Cache.Insert("EMP_NAME", "Shubhabrata", null, 
             Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(60));
How to Know When an Item is Removed?

In the above examples, we learned how to remove an item from the cache but sometimes we want to know when an item is removed from the cache. Yes, it is possible by using cache callbacks. In the error details example shown above, the cached item expires whenever the errors.xml file changes. Suppose we want to update the cache with the latest error details. To find out when the error details are removed from the cache, we can use cache callback for further processing (reloading the error details again to the cache).

The following example shows how to use cache callback to handle the item expiration scenario:

C#
private void AddItemsToCache()
{    
    int empNum = 5435;
    CacheItemRemovedCallback onEmpDetailsRemove = 
                    new CacheItemRemovedCallback(EmpDetailsRemoved);
    Cache.Insert("EMP_NUM", empNum, null, 
                              Cache.NoAbsoluteExpiration, 
                              Cache.NoSlidingExpiration, 
                              CacheItemPriority.Default, 
                              onEmpDetailsRemove);
}

private void EmpDetailsRemoved(string key, object val, 
                              CacheItemRemovedReason reason)
{
    //When the item is expired
    if (reason == CacheItemRemovedReason.Expired)
    {
        //Again add it to the Cache
        AddItemsToCache();
    }
}

In the above example, you must have noticed the parameter CacheItemPriority used along with the callback method. CacheItemPriority is used to set the priority of the item that we are adding to the cache. The priority needs to be set to tell the cache that in case of low memory in the server, remove the cached items to free memory based on the priority set. This process is known as scavenging.

.NET Remoting

You might be thinking how .NET remoting can be used for data caching? The same question came to my mind when I heard about it for the first time. As you know, the .NET remoting singleton object shares the same instance with multiple clients so singleton objects can be used to store and share data between different client invocations. Since .NET remoting can be used outside the process and machine, this option is very useful when we want to cache data and share it across servers and users particularly in a web farm scenario. In this approach, we can store the data as member variables of singleton remoting object and provide methods to read and save data. But while implementing this, we need to ensure that the remoting object used as cache is not destroyed by the garbage collector. For that, we will have to set the remoting cache object's lease period to infinite so that the lease period never times out. We can do that by overriding the InitializeLifetimeService method of MarshalByRefObject and return null from this method. But the only issue with this approach is performance. As the analysis says, the performance is very poor in this approach when compared to other approaches. Anyways, it is up to the application designers/developers to analyze different options and use the best one that suits the requirement.

Memory-mapped Files

You all know what a memory-mapped file is. It is basically about mapping a file on disk to a specific range of addresses in the application's process address space. This option allows different processes to use the same data by increasing the application performance. As using memory-mapped file is not very popular among .NET developers, I would personally not suggest this approach as implementing this involves a lot of complexities and also .NET Framework doesn't support this. But if anyone is very much interested in using this approach, then they will have to develop their own custom solution as per their own requirement.

Static Variables

We use static variables for storing data or objects globally so that they can be accessed during the life of the application. Similarly, in ASP.NET, we can use static objects for caching data and we can also provide methods to retrieve and save data to the cache. As static variables are stored in the process area, performance wise it is faster. But since it is very difficult to implement expiration policies and dependencies in case of static variables, I generally prefer ASP.NET cache object over this option. Another problem is that the custom static cache object has to be thread-safe which has to be implemented carefully.

Custom static cache object can be defined as shown below:

C#
public class CustomCache
{
    //Synchronized to implement thread-safe
    static Hashtable _myCache = 
             Hashtable.Synchronized(new Hashtable());
    
    public static object GetData(object key)
    {
        return _myCache[key];
    }
    
    public static void SetData(object key, object val)
    {
        _myCache[key] = val;
    }
}

Database

We can also use a database for storing data and sharing the data across users and machines. This approach is very useful when we want to cache large amounts of data. Using this approach for storing small amount of data is not a good idea because of performance. For storing small amount of data, we should go for other ASP.NET in-process caching mechanisms. As the data needs to be stored in a database, all the objects need to be XML serialized so that it is easier to store and retrieve. We can also use other types of serialization formats available in the .NET Framework.

ASP.NET Page Output Caching

Sometimes in our application, in some pages, the output generally doesn't change for a specific period of time. For example, in a HR website, the salary details of an employee don't change very frequently and it changes only once in a month. Generally, it changes only on the 1st day of every month. So during a month, the salary details page will show the same details for a particular employee. So in this case, isn't it a good idea to cache the page somewhere in the server to avoid business calculation processing, calls to database and page rendering logic every time the employee wants to see his salary details. In my opinion, Yes! It is a very good idea. To achieve this, ASP.NET provides a feature to store the output of a page in the server for a specific period of time. It also provides features to store a fragment of a page which is known as Page fragment caching. Here, I am not going to talk much about Page Output Caching as there are other articles available in the Internet that cover this mechanism in detail. This would a very lengthy section if I start discussing it. I am planning to cover this in another article.

ASP.NET
<!-- VaryByParm - different versions of same page will be 
cached based on the parameter sent through HTTP Get/Post
Location - where the page is cached -->
<%@OutputCache Duration="60" VaryByParam="empNum" 
                                       Location="Server"%>

Let us do a comparison between the different options that we have discussed so far:

Option Works in Web Farm scenario? Remarks

ASP.NET Session State
- InProc
- StateSerer
- SQLServer


No
Yes
Yes

Unlike other option, it stores only user session specific data
ASP.NET Application Object No  
ASP.NET Cache Object No  
.NET Remoting Yes  
Memory-Mapped files No  
Static Variables No  
Database Yes  
ASP.NET Page Output Caching No  

Client Side Cache Management

In the previous sections, we discussed about the different caching options available in the server side but sometimes, we may need to cache data or pages in the client side to improve application performance. Using this mechanism reduces the load on server but this mechanism has some security issues as we cache the data in client side. There are different options available for client-side caching. I am going to talk briefly about a few of them.

Cookies

Cookie is a very familiar term in the web development environment. Cookie is a client-side storage that is sent to the server for each request and also received as response back from the server. Because of its size limitation (4096 bytes), it should be used for storing small amount of data. Expiration policies can be set for cookies to invalidate the items after a certain period of time. The following example shows how Cookie can be used in an ASP.NET application:

C#
if (this.Request.Cookies["MY_NAME"] == null) 
{ 
    this.Response.Cookies.Add(new HttpCookie("MY_NAME", 
                                       "Shubhabrata Mohanty")); 
} 
else 
{ 
    this.Response.Write(this.Request.Cookies["MY_NAME"].Value); 
}

ViewState

ASP.NET ViewState is a new concept. Here, the data related to the pages and controls are stored in ViewState which retains the values across multiple requests to the server. If you remember correctly, in VB-ASP applications, we used to store data across multiple requests using hidden fields. ViewState is actually implemented internally as hidden fields in ASP.NET, but here, the data is hashed to improve security as against hidden fields. To see how ViewState is implemented, you can view the source of the page in which ViewState is used in the Internet browser. ViewState should not be used to store large amounts of data as it is passed to the server for each request.

C#
protected void Page_Load(object sender, EventArgs e) 
{
    if (this.ViewState["MY_NAME"] == null) 
    { 
        this.ViewState["MY_NAME"] = "Shubhabrata Mohanty";
    } 
    
    //txtName is a TextBox control 
    this.txtName.Text = this.ViewState["MY_NAME"].ToString(); 
}

Hidden Fields

Hidden fields are very popular among VB-ASP web developers. Hidden field is similar to any other control in a page but the visible state of this control is always false. Like ViewState, we should not use it for storing large amounts of data.
Note: Similarly, hidden frames can be used to cache data in the client side. But please note that hidden frames are not supported by all Internet browsers.

ASP.NET
<!--In ASP.NET-->
<asp:HiddenField ID="myHiddenField" Value="Shubhabrata" 
                                             runat="server" />
<!--In HTML-->
<input id="myHiddenField" type="hidden" value="Shubhabrata" />

Microsoft Internet Explorer Caching

Since I am talking about ASP.NET, then why not discuss about another caching capability from Microsoft? Microsoft Internet Explorer provides caching mechanism to cache pages in the client side. This can be set using the EXPIRES directive added to the header of the pages or by setting it manually in IIS. In IIS, go to the HTTP header tab of the properties window of the page and then select the Enable Content Expiration checkbox. We should use this option for caching static pages and images in the client side.

Conclusion

Hope I have explained all the different options clearly. Now it's time to go back to our desk and start implementing each of these options. Implementing any of these options should be done purely on the basis of our application requirement and proper evaluation.

I request all readers of this article to provide their valuable comments.

Reference

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.