ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


ASP.NET Caching

by Wei-Meng Lee
12/30/2002

Caching is an important concept in computing. When applied to ASP.NET, it can greatly enhance the performance of your Web applications. In this article, I will discuss some of the techniques for caching ASP.NET pages on the server side.

Output Caching

Output caching caches the output of a page (or portions of it) so that a page's content need not be generated every time it is loaded.

Consider the following page where a user logs in to a site and reads the latest news. The page will display the user's name and retrieve the latest news since he last logged in.

Displaying the latest news
Figure 1: Displaying the latest news

In a typical ASP.NET page, every time the user views the page, the Web server will have to dynamically generate the content of the page and perform the relevant database queries (which is a very expensive task to do).

Considering the fact that the page does not change for a certain period of time, it is always a good idea to cache whatever is non-static so that the page can be loaded quickly.

Let's add a new OutputCache page directive to the ASP.NET page:

 
   <%@ OutputCache Duration="15" VaryByParam="*" %>
   <%@ Page Language="vb" AutoEventWireup="false"
Codebehind="WebForm1.aspx.vb" Inherits="Caching.WebForm1"%>
 

The OutputCache directive specifies the various criteria for caching the page. In this case, the page will be cached for 15 seconds. This means that if the page is loaded and within the next 15 seconds it is refreshed, the Web server will serve the same content without needing to dynamically regenerate the page. This can be verified by the time displayed on the page.

Programming ASP .NET

Related Reading

Programming ASP .NET
By Jesse Liberty, Dan Hurwitz

The VaryByParam attribute specifies how the caching should be performed, based on the query string supplied to the page. For example, if I used the following URL:


http://localhost/Caching/WebForm1.aspx?name=John&newsid=12

The query string passed to the page is name=John&newsid=12. So now, if I change the VaryByParam attribute to:


<%@ OutputCache Duration="15" VaryByParam="Name" %>

The page will be cached according to the Name key. This means that if I issue the following two URLs, the second page will still be from the cache, as the cache will only be refreshed if the Name value changes:


http://localhost/Caching/WebForm1.aspx?name=John&newsid=12
http://localhost/Caching/WebForm1.aspx?name=John&newsid=45

The following URLs will cause the second page to be refreshed:


http://localhost/Caching/WebForm1.aspx?name=John&newsid=12
http://localhost/Caching/WebForm1.aspx?name=Michael&newsid=12
 

If you want to cause the page to be regenerated if the Name and NewsID keys change, then you simply add the NewsID key into the VaryByParam attribute:


<%@ OutputCache Duration="15" VaryByParam="Name;NewsID" %>

In this case, this is equivalent to using a "*", which means all keys will cause the page to be regenerated:


<%@ OutputCache Duration="15" VaryByParam="*" %>

If you want to cache the page regardless of query string, you can use the value "none":


<%@ OutputCache Duration="15" VaryByParam="none" %>

Caching Objects in a Page

While output caching is useful, a much more powerful and flexible application would be to cache individual objects in your Web application.

For example, I have a Web application that retrieves news from a SQL Server database and displays the news titles in a ComboBox. As users are expected to read the news by selecting the title from the ComboBox, I want to minimize the number of times that the application connects to SQL Server for retrieving the news content. So I cache the DataSet containing the news.

Caching the DataSet containing the news
Figure 2: Caching the DataSet containing the news

The first time the page is loaded, the user has to click on the Get News button to get the news from the SQL Server:


Private Sub Button1_Click(ByVal sender As _
                                System.Object, _
                          ByVal e As System.EventArgs)_
                          Handles Button1.Click
    Dim ds As DataSet
    ds = loadData()
    Dim i As Integer
    DropDownList1.Items.Clear()
    For i = 0 To ds.Tables("News").Rows.Count - 1
        Dim item As New _
ListItem(ds.Tables("News").Rows(i).Item(2).ToString,_
ds.Tables("News").Rows(i).Item(0).ToString)
        DropDownList1.Items.Add(item)
    Next
End Sub

This calls the loadData() method, which loads the data from SQL server and then displays the data in the ComboBox.


Public Function loadData() As DataSet
    Dim ds As New DataSet
    If Cache("News") Is Nothing Then
        Dim sql As String = "SELECT * FROM NewsHeader"
        Dim conn As New SqlConnection ( _
          "server=localhost; uid=sa;" & _
          " password=; database=News")
        Dim comm As New SqlCommand(sql, conn)
        Dim dataAdapter As New SqlDataAdapter(comm)
        dataAdapter.Fill(ds, "News")
        Cache("News") = ds
    Else
        ds = CType(Cache("News"), DataSet)
    End If
    Return ds
End Function

Note that I use a Cache object to see if it already contains a copy of the DataSet (which contains the news). If it doesn't (Is Nothing), I will retrieve it from the SQL Server and then store it using the Cache object, using the key "News". Subsequent requests to the loadData() method will load the data from the cached DataSet.

When the user selects a new title from the ComboBox, the news content is retrieved from cache:


Private Sub DropDownList1_SelectedIndexChanged(ByVal _
        sender As System.Object, _
        ByVal e As System.EventArgs) Handles _
        DropDownList1.SelectedIndexChanged
    Dim index As Integer = _
        DropDownList1.SelectedIndex()
    Dim ds As DataSet
    ds = CType(Cache("News"), DataSet)

    If ds Is Nothing Then
        ds = loadData()
    End If
    Label3.Text = _
ds.Tables("News").Rows(index).Item(1).ToString()
    Label1.Text = _
ds.Tables("News").Rows(index).Item(3).ToString()
End Sub

Note that you have to perform a type conversion using the CType() method. Using this method, I can reduce the number of connections I have to make to SQL Server. This will improve the performance of my Web application.

What happens if there are new items in the database and you need to display them? You need to explicitly clear the cache via the Clear Cache button:


Private Sub Button2_Click(ByVal sender As _
            System.Object, ByVal e As _
            System.EventArgs) Handles Button2.Click
    Cache.Remove("News")
End Sub

Smart Caching with Dependencies

The caching technique described in the last section is useful but not brilliant. You don't expect the reader to know when to clear the cache. A better way would be to design the cache to automatically refresh itself when some external events happen. Let me extend the example.

Suppose our application loads a text file containing the header (that changes daily) for the day's news:

Displaying the header
Figure 3: Displaying the header

Assuming that the news changes daily, so it is logical that whenever the header changes, the data in the cache should be refreshed as well. So there is a dependence on the header.txt file. To express the cache's dependency on the file, use the System.Web.Caching.CacheDependency class.

Hence, rewriting my loadData() method:


Public Function loadData() As DataSet
    Dim ds As New DataSet
    Dim header As String
    Dim file As New _
 System.IO.StreamReader(Server.MapPath("header.txt"))
    header = file.ReadLine
    file.Close()
        
    lblHeader.Text = header

    If Cache("News") Is Nothing Then
        Dim sql As String = "SELECT * FROM NewsHeader"
        Dim conn As New _
            SqlConnection("server=localhost; " & _ 
            "uid=sa; password=; database=News")
        Dim comm As New SqlCommand(sql, conn)
        Dim dataAdapter As New SqlDataAdapter(comm)
        dataAdapter.Fill(ds, "News")

        Dim depends As New _
 System.Web.Caching.CacheDependency _
        (Server.MapPath("Header.txt"))
        Cache.Insert("News", ds, depends)
    Else
        ds = CType(Cache("News"), DataSet)
    End If

    Return ds
End Function

Notice that I have used the insert() method of the Cache class to insert the DataSet and file dependency:


        Cache.Insert("News", ds, depends)

From now on, if the content of the header.txt file is changed, the content in the cache would be cleared.

Time-Based Caching

Another technique for caching is based on time. For example, the cache can expire on a certain date, or it will only be available for a certain period of time. There are two ways in which you can use time-based caching:

  1. Absolute Expiration. The cache is set to expire on a particular date and time.

  2. Sliding Expiration. The cache is set to expire after a certain period of inactivity.

For example, if you have:


'===Absolute expiration===
Cache.Insert("News", ds, Nothing, _
              DateTime.Now.AddMinutes(2), _
              Cache.NoSlidingExpiration)

The cache is set to expire exactly two minutes after the user has retrieved the data.

For sliding expiration, you can use:


'===Sliding expiration===
Cache.Insert("News", ds, Nothing, _
              Cache.NoAbsoluteExpiration, _
              TimeSpan.FromMinutes(1))

This will cause the cache to be cleared if the user does not reload the page within one minute. If the user reloads the page within the one-minute time frame, the data in the cache will be valid for another minute.

Use Caching Sparingly

As in life, anything that is good comes with a price. Enabling caching in ASP.NET causes the cache content to be stored in the Web server's memory. Caching takes up valuable system resources and can easily eat up all of your available memory.

When server memory runs out, the contents of your cache will be evicted. The criteria for cache eviction is based on priority, which you can optionally set when adding data to your cache:


Cache.Insert("News", ds, Nothing, _
              Cache.NoAbsoluteExpiration, _
              TimeSpan.FromMinutes(1), _
              System.Web.Caching.CacheItemPriority.High, _
              Nothing)

There are seven levels of priority:

NotRemovable, High, AboveNormal, Default, Normal, BelowNormal, and Low.

Conclusion

Caching of Web pages is an effective way of increasing performance while minimizing the use of precious server resources. Choosing the appropriate level for caching data is important for balancing caching versus memory usage. One of the most effective strategies to good Web application performance is to cache data only when necessary.

Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.