ASP.NET VERSIONS: 1.x | 2.0
ASP.NET Page Output Caching
An Effective Page Output Caching Layer Can Benefit Your Application
By Dino Esposito
Do you remember the bug in the Pentium s floating point processor that was in the news for a while some years ago? Simply put, that bug could cause applications to return incorrect results for certain math operations with certain numbers. The bug was caused by a bad value mistakenly inserted in one of the reference tables used by the floating point processor to speed up its own calculation work. A reference table is a matrix of precalculated, cached values that floating point units use to accelerate calculation. Well, how does this relate to ASP.NET and page output caching? Caching data any sort of data is always a way to improve performance in any sort of application.
What is page output caching, ultimately? It s a dynamically changing reference table that contains HTML markup instead of floating point numbers. The ASP.NET HTTP runtime, like the CPU s floating point unit, looks up the table for a valid entry. If a matching entry is found, it is loaded and used instead of processing the request. If no valid entry is found, then the request is processed as usual. The resulting page output will be cached in the internal table, and will stay there, ready to serve the next incoming requests for the same page.
As you can imagine, page output caching can provide several benefits to your applications. However, to make sure that there are no snags or loose ends, you should carefully consider a few caveats.
Look Before You Cache
The key advantage that page caching brings to the table is pretty obvious your application s throughput, defined as the number of served requests per second, is substantially increased. A cached page is not supposed to stay in the cache indefinitely. Giving pages a thoughtful duration contributes to keeping the overall execution time low. Even a duration of one second can mark the difference; but sometimes a too-long duration, combined with a relatively infrequent use of the page, can raise more issues than it attempts to resolve. Page output caching is a way to stave off processing time as a result of bypassing the ASP.NET HTTP runtime. The more a cached page is requested, the more you gain in terms of shorter response time. The less a cached page is requested, the more you pay in terms of memory occupation. The phenomenon gets even sharper if the page has been assigned a long cache duration. A short duration, in fact, would invalidate the page sooner, thus freeing some valuable memory.
Which kind of pages take most advantage of caching features? Good candidates for output caching are relatively static, readonly pages such as those describing a product or showing periodical reports. In general, the ideal candidate for the cache is any page that is frequently accessed and displays global information that all users share. You are also encouraged to cache pages that show live data, as long as it is acceptable to your users to have data delayed by some interval. For example, it is fine to cache a page showing a chart that is updated every hour. In this case, you can set the cache duration to 60 minutes and have both updated data and maximum performance.
As mentioned, a product page seems to be the perfect candidate for caching. However, a product page is normally a unique page that selectively displays product information based on a query string parameter:
How would it work in this case? The ASP.NET page-caching mechanism allows you to specify a handful of parameters to vary pages and cache multiple copies of the same page. You can choose a query string attribute (for example, the id attribute in the sample URL above) and have ASP.NET save a copy of the page for each distinct value invoked for the attribute. Let s drill down the capabilities of the @OutputCache directive the syntax element that enables and configures page output caching.
Explore the @OutputCache Directive
To be precise, output caching can either be configured declaratively through the @OutputCache directive or programmatically through the interface of the HttpCachePolicy class. The directive works with both ASP.NET pages and user controls, and lets you declaratively set the caching capabilities of the page or control. The directive accepts a variety of attributes, a couple of which (Duration and VaryByParam) are mandatory.
The Duration attribute indicates for how long the system should keep the page output cached. The Duration is expressed in seconds. The VaryByParam attribute allows you to vary the cached output depending on the contents of the query string or the field values posted through a POST statement. For example, the following declaration will cache the page for one minute, regardless of any query string or form parameters:
<%@ OutputCache Duration="60" VaryByParam="None" %>
The @OutputCache directive is made up of six attributes that indicate the location of the cache, its duration, and the arguments to use to vary page caching (see Figure 1). As mentioned, VaryByParam is a required attribute whose absence makes the page throw an exception. The empty string is not an acceptable value; if you don t want to vary the page output by any parameters, simply set it to None.
The time, in seconds, that the page or user control is cached.
Specifies where to store the output of a page. The attribute takes its value from the OutputCacheLocation enumeration. The default is Any.
A semicolon-separated list of strings that lets you maintain distinct cached copies of the page based on the browser type or user-defined strings.
A semicolon-separated list of HTTP headers.
A semicolon-separated list of strings representing query string values sent with GET method attributes, or parameters sent using the POST method.
A semicolon-separated list of strings representing fully qualified names of properties on a user control. Only valid for caching user controls; don t use it with ASP.NET pages.
Figure 1: The six attributes of the @OutputCache directive.
In addition to GET and POST parameters, a page can be varied by HTTP headers and by custom techniques. Imagine you have a page with the following directive:
<%@ OutputCache Duration="60" VaryByParam="None"
What s the result? The page is cached and stored for one minute if the request contains a header named DinoE. The first time a similar request comes in, the page is processed by the pipeline and its output is cached to memory. Next requests for the same page that don t include the header are served as usual; for others, the markup is retrieved from the cache and served to the user.
The VaryByCustom attribute allows you to vary the versions of page output by a sort of logical hash calculated on the page. The VaryByCustom attribute is set to a user-defined string; this string is then passed to an overridable method on the HttpApplication class GetVaryByCustomString.
GetVaryByCustomString uses the input string as an argument to calculate the vary-by string, which is then returned to the pipeline. In summary, the string to vary the page output is programmatically determined and identified by a public name that is the string you assign to VaryByCustom.
The default implementation of VaryByCustom supports one special string (browser), which indicates that pages will be varied on the browser type:
<%@ OutputCache Duration="60" VaryByParam="None"
The real string used to vary the page output is composed by the browser name and major version number. In particular, the string is what returns the Type property of the HttpBrowserCapabilities object. To override this behavior, or to add more custom strings, add the code shown in Figure 2 to the global.asax file.
public override string GetVaryByCustomString(
HttpContext context, string custom)
// Handles the Browser string
if (custom == "browser")
// Handles your own strings
if (custom == "...")
Figure 2: Overriding GetVaryByCustomString in the global.asax file.
Under IIS 5.0, the output caching mechanism requires that a new HTTP application be created, although only for a short time. A system HTTP module hooks up the ASP.NET request and verifies that a cached copy for the page exists. If a copy is found, it is retrieved from the cache and served. Output caching is certainly effective in this way, but it would be even more beneficial to applications if implemented within IIS. This is exactly what happens with IIS 6.0. With Microsoft Windows Server 2003 and the IIS 6.0 process model, the output caching is integrated in the Web server, resulting in much better performance and responsiveness. In other words, IIS 6.0 provides output caching capabilities that an ASP.NET page can control through the standard API. Pages are no longer cached within the ASP.NET worker process, but instead are stored within the IIS boundaries. As a result, no extra HTTP application must be created to serve a request from the cache.
The output cache can be located in various places, either on the client that originated the request or the server. It can also be located on an intermediate proxy server. By default, a cacheable page is cached anywhere client, server, and proxy, if any exists. You set the Location attribute of the @OutputCache directive to decide about the location. If the chosen value is Client, then the Expires header is added set to the proper duration. If the location is Server, then the page output is stored in the Cache object. Another feasible value is DownStream. In this case, the page can be cached both on the client and in the memory of any intermediate proxy. The Expires header is set according to the value of the Duration attribute, and no copy of the page output is maintained by ASP.NET.
Portions of the Page
The capability of caching the output of Web pages is helpful, but sometimes caching the entire content of a page is not possible, or is simply impractical. Some pages, in fact, are made of pieces with radically different features as far as cacheability is concerned. In these situations, being able to cache portions of a page would be an incredible added value. ASP.NET user controls are the solution to the problem because they support output caching features in much the same way a page does. If you want to be able to cache the output of a portion (or various portions) of a page, you simply move that portion of the page to a user control and configure the @OutputCache directive of the user control accordingly. The VaryByControl attribute of the directive (which is specifically designed for cacheable user controls) lets you cache copies of the user control per each distinct value of some control properties.
What s New in ASP.NET 2.0
In the Beta 1 version of ASP.NET 2.0, @OutputCache supports two additional attributes, Shared and SqlDependency. The Shared attribute indicates whether the output of a user control is shared among multiple pages using that control. The default is false, meaning that each page will have its own copy of the control s output. As you can see, the Shared attribute works only for user controls. The SqlDependency attribute indicates a dependency on the specified table on a given SQL Server database. Whenever the contents of the table change, the page output is removed from the cache.
The SqlDependency attribute binds the output of the containing page to the state of a particular table in a SQL Server 7.0 or SQL Server 2000 database. Here s an example:
<% @OutputCache Duration="3600" VaryByParam="none"
A page or a user control that contains this code snippet has its output cached for an hour or until a record changes in the Employees table in the Northwind database. Note that the Northwind string here is not the name of a database, but the name of an entry in the new <cache> section of the configuration file. That entry contains detailed information about the connection string to use to reach the database. For the mechanism to work, the database and the table must be enabled for change notification.
Multiple dependencies can be specified by separating multiple database:table pairs with a semicolon in the value of the SqlDependency attribute. If you re setting the attribute while using SQL Server 2005, you must give the attribute the name of a command notification object as its value. The command notification object embeds information about the connection string, the database, and the particular query run. In this case, the page output caching mechanism will benefit from the SQL Server s capabilities of monitoring for changes in the results of a query and not a full table.
To finish off with the changes in the upcoming version of ASP.NET, a word about the substitution mechanism is in order. In ASP.NET 1.x, you can cache the whole page or a part of it. There s isn t anything that allows you to cache all the page but a portion. This is just what you get in ASP.NET 2.0 through the <asp:substitution> tag. Any markup wrapped by the tag is considered dynamic and never cached, regardless of the settings of the @OutputCache directive.
Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.DotNet2TheMax.com, a popular portal for .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.