May 15 2007

Server-side Output Caching

Using output caching to improve the performance of your ASP.NET Web application.

Here’s an easy way to dramatically improve performance and scalability of your ASP.NET Web applications: enable output caching. It takes less than one minute to enable caching on pages that aren’t personalized for individual users, such as news listings, search results and product catalogs. If your content is personalized or extremely time sensitive, you can significantly reduce ASP.NET rendering time with just a couple of hours of work.

Even the most dynamic sites can benefit from caching, through these techniques:

  • page caching, which caches an entire rendered ASP.NET page;
  • adding user control caching and write substitution, which cache portions of a page;
  • discarding cached results when a database is updated;
  • programmatically determining whether to use a cached page or rerender the ASP.NET page.

Server-side output caching is a quick and easy way to increase your Web application’s performance and scalability. Implementing caching in a typical Web application that includes some dynamic content can reduce processor utilization by 90 percent across all tiers of an application, including the front-end Web server, application servers and database servers. Because you control which portions of a page are cached and when the cache is updated, you can cache personalized pages and pages that display database queries without worrying about ever serving stale data. Maybe management could take some of the budget that they save on server upgrades and give the developers a raise!

How to Enable Page Caching

With ASP.NET 2.0, you can enable server-side page output caching to store a copy of pages after ASP.NET has rendered them. If the same or a different user requests the page, ASP.NET serves the cached copy about as fast as it would serve static HTML because ASP.NET completely bypasses the rendering process.

To enable server-side page output caching, add the following directive to the top of your .aspx page:

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

Notice that this example of the OutputCache directive includes the two required parameters:

  • Duration: The length of time, in minutes, ASP.NET will use the cached output before rerendering the page.
  • VaryByParam: A list of query parameters that, if the values are different, should be cached separately as if it were a different Web page.

Let’s say you have an ASP.NET page to browse your inventory by category, and a typical URL looks like this:


Obviously, you wouldn’t want the output for the “widgets” category to be returned for the “gadgets” category, and each page number would have unique results, so you would use the following directive at the top of the browse.aspx page:

<%@ OutputCache Duration="15" VaryByParam="category; page" %>

This directive would cause ASP.NET to cache both pages 1 and 2 of the “widgets“ category separately, and rerender the page after 15 minutes. The “gadgets” and “widgets” categories would be cached separately, too. To create a separate cache for every parameter, set VaryByParam to “*”.

How to Cache Portions of a Page

Many pages are personalized to show the user’s name, customized stock quotes or local ads. The directive described in the previous section would also cache personalized content, and cause it to be sent to the wrong users. To work around this, move any personalized content into .ascx user controls and add the “@ OutputCache” directive to the user controls (even if you simply need to disable caching for that control). ASP.NET will cache the user controls separately from the parent page and combine the cached output for every page that needs to be rendered. To disable output caching for a control, add the following directive to the top of the .ascx page:

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

Instead of creating separate user controls for dynamic content, you may use substitution to dynamically generate HTML for parts of a page. To use substitution, follow these steps:

  1. Create a callback method that accepts an HttpContext object and returns a string containing the HTML content, as the following example demonstrates:

    Shared Function GetDate (ByVal context As HttpContext) As String
        return DateTime.Now.ToString()
    End Function

    Public Static String GetDate (HttpContext context)
        return DateTime.Now.ToString();
  2. Use the designer to add a Substitution control to your .aspx page.
  3. In the designer, set the Substitution.MethodName property to the name of the method you created in step 1. To run the previous example, you would set Substitution.MethodName to "GetDate."

ASP.NET will automatically run the method you specify for the Substitution.MethodName parameter each time the page is requested, even if it uses the cached output for the rest of the page.

Creating an SQL Server Database Dependency

Thinking back to the example of using page output caching when browsing inventory by category, you might be able to tolerate new products not immediately appearing in cached pages, but you don’t have to. If you use Microsoft SQL Server 2005 on the back end, you may create a Structured Query Language database dependency that automatically invalidates the cache by setting OutputCache’s SqlDependency parameter to the value CommandNotification,  demonstrated as follows:

<%@ OutputCache SqlDependency="CommandNotification" Duration="15" VaryByparam="none" %>

SQL Server 2005 will automatically notify ASP.NET if the results of any queries used in the page have changed, and ASP.NET will rerender the cached page on the next request. For information about how to create queries that support query notification, read “Creating a Query for Notification” at http://msdn2.microsoft.com/en-US/library/ms181122.aspx. If any of your queries don’t support notification (for example, if you used “SELECT *”), no caching will occur.

If you use Microsoft SQL Server 7.0 or Microsoft SQL Server 2000, you may achieve similar functionality by configuring the database using the Aspnet_RegSql.exe command (in your “%windir%\Microsoft.NET\Framework\<build>\” folder) to set up the database and table to support dependencies. For more information, read “Improved Caching in ASP.NET 2.0” at http://msdn2.microsoft.com/en-us/library/ms379559(VS.80).aspx.

Deciding Whether to Use a Cached Page

If you would rather run code to determine whether to use the cached version of a page or rerender it, you may create an HttpCacheValidateHandler for the cached page and set the HttpValidationStatus parameter to one of the following three values:

  • HttpValidationStatus.Invalid: Remove the cache and dynamically generate the page.
  • HttpValidationStatus.IgnoreThisRequest: Dynamically generate the page without overwriting the cache.
  • HttpValidationStatus.Valid: Return the current cache without dynamically generating the page.

The following code demonstrates how to handle the event:

Public Shared Sub ValidateCache (ByVal context As HttpContext, _
        ByVal data As [Object], ByRef status As HttpValidationStatus)
    ‘ TODO: Add logic to set one of the following values
    status = HttpValidationStatus.Invalid
    status = HttpValidationStatus.IgnoreThisRequest
    status = HttpValidationStatus.Valid

public static void ValidateCache (HttpContext context,
        Object data, ref HttpValidationStatus status)
    // TODO: Add logic to set one of the following values
    status = HttpValidationStatus.Invalid
    status = HttpValidationStatus.IgnoreThisRequest
    status = HttpValidationStatus.Valid

After creating the method, define it as the event handler in the Page_Load method:

Protected Sub Page_Load (ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles Me.Load

    Response.Cache.AddValidationCallback ( _
        New HttpCacheValidateHandler (AddressOf ValidateCache), Nothing)

protected void Page_Load (object sender, EventArgs e)
    Response.Cache.AddValidationCallback (
        new HttpCacheValidateHandler (ValidateCache), null);

Tony Northrup, Microsoft MVP, MCSE, MCTS and CISSP, is a Windows consultant and author residing in Massachusetts. He has written more than a dozen books covering Windows networking, security and development.