- Cache content
- Improve the performance of Web applications by using output caching
- Manage the cached content through the OutputCache directive
- Manage the cached content through the HttpCachePolicy class
This tutorial covers ASP.NET's support for caching output. In previous tutorial, we saw what an impact data caching could make on your application. By avoiding round-trips to the database, you can make parts of your Web site run much faster than they otherwise would. In addition to data caching, ASP.NET supports output caching.
After completing this tutorial, you will be able to
Cache content
Improve the performance of Web applications by using output caching
Manage the cached content through the OutputCache directive
Manage the cached content through the HttpCachePolicy class
This tutorial covers ASP.NET's support for caching output. In Tutorial 14, we saw what an impact data caching could make on your application. By avoiding round-trips to the database, you can make parts of your Web site run much faster than they otherwise would. In addition to data caching, ASP.NET supports output caching.
After spending a bit of time watching the whole page-rendering process, you now know it can be pretty involved. A lot happens between the time a page loads and the final closing tag is sent to the browser. For example, the page may require database access. It may have a number of controls declared on it. Furthermore, perhaps some of those controls are the more complex controls like the DataList or the GridView whose rendering process is expensive. All these things usually take time to process.
Just as you can bypass whole round-trips to a database by caching data in memory, you may configure ASP.NET to bypass the whole page-rendering process and send back content that's already been rendered once. This is called output caching.
As you surf the Web, you see all manner of pages. Some sites churn their content very quickly, while others change much more slowly. Some pages have portions that change while other portions of the page remain static. If you have a page whose content changes infrequently, you may cache the output instead of regenerating it every time a request comes in.
At the outset, turning on output caching is easy. To set up caching, place the OutputCache directive on the page. It's a separate directive, like the Page directive. The OutputCache directive enables caching and provides certain control over its behavior. The following exercise introduces caching output.
Create a Cacheable Page
Create a new Web site named OutputCaching.
Open the Default.aspx file and insert the OutputCache directive near the top, immediately after the Page directive. For now, set the Trace attribute to false (we'll turn it on a bit later when we look at caching User Controls). At the very least, the OutputCache directive needs two things: (1) the Duration attribute to be set, and (2) the VaryByParam attribute to ¡°none.¡± We'll see more about these attributes shortly. The Duration attribute specifies how long the content should be cached. The VaryByParam attribute is for managing caching multiple versions of the page. The following listing shows the syntax of the OutputCache directive. This example caches the page's content for 15 seconds. The code following the output directive was generated by Visual Studio.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" trace="false"%> <%@ OutputCache Duration="15" VaryByParam="none" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> </div> </form> </body> </html>Update the Page_Load method to print the date and time that this page was generated, like so:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Response.Write("This page was generated and cached at: " + DateTime.Now.ToString()); } }The first time the content is produced, the Page_Load method runs and produces the following output:
No matter now many times you refresh the browser (you may do this by pressing F5 while running Internet Explorer within 15 seconds of first accessing the page), ASP.NET will grab the cached content and display that. As soon as 15 seconds has expired, ASP.NET runs the page in the normal way, calling Page_Load, regenerating the content, and caching it again. The following graphic illustrates the new page accessed just moments later than 15 seconds following the first hit:
To get an idea as to how caching content might improve performance, add a small amount of code to the Page_Load method to put the executing thread to sleep for perhaps 10 seconds (this is to simulate an expensive content-generating routine). You'll need to use the System.Threading namespace to access the threading functions.
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Threading; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Thread.Sleep(10000); Response.Write("This page was generated and cached at: " + DateTime.Now.ToString()); } }Now surf to the page. Notice how long the page took to load (about 10 seconds). Immediately refresh the page. Notice the browser displays the content right away¡ªwithout the long wait time. For pages that are expensive to generate and that don't change very often, caching the content represents an enormous performance boost for your Web site¡ªespecially as the number of clients increases.
In some cases, it's enough to blindly cache the content of certain pages by simply putting the OutputCache directive in the page. However, sometimes you need a bit more control over what's happening in the output cache. ASP.NET supports a number of parameters you may use to manage the way the cache functions. You may control the output caching behavior by either changing the parameters in the OutputCache directive or by tweaking the HttpCachePolicy property available through the Response object.
Modifying the OutputCache Directive
It's often very useful to be able to govern output caching. For example, some pages present the exact same content to all the users who access the page. In that case, caching a single version of the content is just fine. However, there are other circumstances in which sending the same content to everyone is inappropriate. The easiest way to control the behavior of output caching is to modify the OutputCache directive.
One obvious case in which controlling the cache is important is while caching different versions of content for different browsers making requests. Different browsers often have different capabilities. If you send content requiring a feature not supported by all browsers, some browsers making requests will get a spurious response. The VaryByCustom parameter within the OutputCache directive allows you to cache different content based on different browsers.
Controlling the output caching is also important when your page renders content based upon the parameters that are sent within the query string. For example, imagine you have a page through which a user has identified himself or herself by typing a name in a text box. The browser will insert that name inside a parameter inside the query list. You may instruct the output cache to cache different versions based on parameters in the query string. For example, users identifying themselves as ¡°John Doe¡± can get a different version of cached content than users identifying themselves as ¡°Jane Smith.¡± The VaryByParam attribute controls this behavior.
Table 15-1 shows a summary of these parameters.
Attribute | Option | Description |
DiskCacheable | true false | Specifies that the output may be written to a disk-based cache (or not) |
NoStore | true false | Specifies that the ¡°no store¡± cache control header is sent (or not) |
CacheProfile | A String | Name of a profile (found in Web.Config) to control output cache settings |
VaryByParam | none * param name | A semicolon delimited list of strings specifies query string values in a GET request or variables in a POST request |
VaryByHeader | * header names | A semicolon delimited list of strings specifying headers that might be submitted by a client |
VaryByCustom | browser custom string | Tells ASP.NET to vary the output cache by browser name and version, or by a custom string; must be handled by an override of GetVaryByCustomString |
Location | Any Client Downstream Server None | Manages which header and metatags are sent to clients to support caching; here are their meanings: Any== page may be cached anywhere Client== cached content remains at browser Downstream== cached content stored both downstream and on the client Server== content cached on the server only None== disables caching |
Duration | number | Number of seconds the page or control is cached |
The following exercise illustrates creating separate versions of cached content based upon how the user identifies himself or herself.
Varying Cached Content by Query String Parameters
Add a TextBox and a Button to the default.aspx page. Give the TextBox an ID of TextBoxName. This will hold the client's name and will server as the parameter controlling the number of cached versions of the page.
Double-click on the button to add a handler for it. In the handler, respond to the user's request by displaying a greeting using the contents of the text box. Also, modify the processing time of the page loading by reducing the amount of time the current thread sleeps (or by removing that line completely):
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Threading; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Thread.Sleep(0); Response.Write("This page was generated and cached at: " + DateTime.Now.ToString()); } protected void ButtonSubmitName_Click(object sender, EventArgs e) { Response.Write("<br><br>"); Response.Write("<h2> Hello there, " + this.TextBoxName.Text + "</h2>"); } }Increase the time that the content will be cached (this example uses a minute). That will give you time to change the contents of the TextBox to view the effects of caching. Also, include TextBoxName as the parameter by which to vary the content within the OutputCache directive.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" trace="false"%> <%@ OutputCache Duration="60" VaryByParam="TextBoxName" %>Surf to the page and type in a name. Click the button to submit the form and note the time stamp of the page. Type a second name into the TextBox and click the button to submit the form. Note the time stamp. Then type the same name you typed the first time. Click the button to submit the form. If you do all this within the 60-second window, you should see the cached versions of the page, which you can discern using the time stamp displayed as part of each page. The following three graphics illustrate the caching varying by the value of the TextBoxName parameter. The first graphic shows the original request:
The second graphic shows a request with a new value for the TextBoxName parameter:
The third graphic shows making a request to the page using the same name as the original request. Note the cached page shows.
There are other ways to modify the VaryByParam attribute. One way is to use the word ¡°none,¡± which means ASP.NET will cache only one version of the page for each type of request (for example, GET, POST, and HEAD). Using an asterisk for VaryByParam ("*") tells ASP.NET to cache as many different versions of the page as there are query string or POST body requests. The example above caches as many different versions of the page as there are unique names typed by users.
Using VaryByHeader in the OutputCache directive tells ASP.NET to generate a separate cache entry for each new header string that comes down (for example, UserAgent and UserLanguage represent HTTP headers that may be sent by the client).
We'll cache a User Control shortly. The VaryByControl attribute lets you cache separate content versions for each page that has a User Control with unique properties.
Finally, VaryByCustom tells ASP.NET to manage separate cache entries dependent upon a couple of factors. The first factor is the browser types and versions. Alternatively, you may provide a custom GetVaryByCustomString method that tells ASP.NET to create separate cached versions of a page based on a custom defined string.
The HttpCachePolicy
The second way to manage the output cache is through the HttpCachePolicy, which is available from the Response class. Listing 15-1 shows a C# representation of a portion of the HttpCachePolicy class.
Listing 15-1
public class HttpCachePolicy
{
public HttpCacheVaryByHeaders VaryByHeaders {get;}
public HttpCacheVaryByParams VaryByParams {get;}
public void AppendCacheExtension(string extension);
public void SetRevalidation(HttpCacheRevalidation
revalidation);
public void SetETag(String str);
public SetValidUntilExpires(bool b);
public void SetExpires(DateTime date);
public void SetLastModified(DateTime date);
public void SetMaxAge(TimeSpan delta);
public void SetNoServerCaching();
public void SetNoStore();
// more methods and properties¡
}When you set up an OutputCache directive, you tell ASP.NET to populate this class during the Page class's InitOutputCache method. The Response object makes the HttpCachePolicy available through its Cache property. The name ¡°Cache¡± is unfortunate because you might easily confuse it with the application data cache. Perhaps CachePolicy would have been a better name for the property to avoid such confusion. You can use the HttpCachePolicy class to control the behavior of the server-side caching as well as the headers used for content caching. The OutputCache directive may also be used to control some of the same aspects as the HttpCachePolicy class. However, some features such as sliding the expiration date or changing the ¡°last modified¡± stamp for a page are available only through the HttpCachePolicy class.
For example, Listing 15-2 shows a page fragment ensuring that all origin-server caching for the current response is stopped. It also sets the last modified date to the current date and time.
Listing 15-2
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(0);
Response.Write("This page was generated and cached at: " +
DateTime.Now.ToString());
Response.Cache.SetNoServerCaching();
Response.Cache.SetLastModified(DateTime.Now);
}
}Caching Locations
In addition to varying the number of cached versions of a page, you may tell ASP.NET where to cache the content. This is controlled through either the Location attribute in the OutputCache directive or by using the HttpCachePolicy class's SetCacheability method.
ASP.NET supports several output caching locations for which you can specify using the OutputCache directive:
- Any
Page can be cached by the browser, a downstream server, or on the server
- Client
Page should be cached on the client browser only
- Downstream
Page should be cached on a downstream server and the client
- Server
Page will be cached on the server only
- None
Disable caching
The HttpCachePolicy also allows you to determine the location of the cached content programmatically. This is done through the HttpCachePolicy.SetCacheability method, which takes a parameter of the HttpCacheability enumeration. The enumeration is a bit easier to read than the attributes used in the OutputCache directive. They include:
- NoCache
Disable caching
- Private
Only cache on the client
- Public
Cache on the client and the shared proxy
- Server
Cache on the server
- ServerAndNoCache
Specify that the content is cached at the server but all others are explicitly denied the ability to cache the response
- ServerAndPrivate
Specify that the response is cached at the server and at the client but nowhere else; proxy servers are not allowed to cache the response
Output Cache Dependencies
We saw how ASP.NET supports data caching in Tutorial 14. The contents of the data cache in ASP.NET may be flushed due to various dependencies. The same is true of ASP.NET output caching. The response object has a number of methods for setting up dependencies based on cached content. For example, you may want to set up a page that renders data from a text file. You can set up a CacheDependency on that text file so that when the text file is changed, the cached output is invalidated and reloaded.
Caching Profiles
One of the problems associated with using the OutputCache directive directly is that the values become hard-coded. Changing the caching behavior means going in and changing the source code of the page. A new feature for ASP.NET 2.0 is the ability to add caching profiles. That way, setting the caching behavior variables is offloaded to the configuration file, and output caching becomes an administration issue and not a programming issue (as it should be).
The Web.Config file may include an outputCacheSettings section that may contain a list of outputCacheProfiles. The outputCacheProfiles are simply key-value pairs whose keys are the output caching variables (such as Duration). When you mention the profile name in the OutputCache directive, ASP.NET will simply read the values out of the configuration file and apply them to the OutputCache directive.
The following exercise illustrates setting up a cache profile instead of hard-coding the values into the page.
Set Up a Cache Profile
Add a cache profile to the Site's Web.Config file. If Web.Config isn't already there, go ahead and add one to the project. Then add a cache profile to Web.Config nested between the system.web elements. Name the cache profile ¡°profile.¡±
<configuration> <system.web> <caching> <outputCacheSettings> <outputCacheProfiles> <add name="profile" duration="60" varyByParam="TextBoxName" /> </outputCacheProfiles> </outputCacheSettings> </caching> </system.web> </configuration>Change the OutputCache directive in the Default.aspx page to use the new profile:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" trace="false"%> <%@ OutputCache CacheProfile=<;$QD>profile" %>Surf to the page. It should work exactly as it did before when the caching values were hard-coded. That is, run the page, type a name, and note the date and time stamp. Type a new name and note the date and time stamp. Type the original name, submit it, and you should see the original cached page appear (as long as you complete the post within the specified time window).
Just as whole pages may be cached, ASP.NET supports caching User Controls as well. Imagine your job is to create a sizeable Web site that allows users to navigate through information via various navigation controls (menus, hyperlinks, and so forth). For example, imagine a part of your page shows links or other navigation controls that lead users to the most recent news, summary information, and other places. The actual content may change, but the links probably don't. If the links don't change very often and the cost of generating that section of the page is expensive, it makes sense to move the functionality into a User Control and apply the OutputCache directive to the User Control. Doing so will cause ASP.NET to cache the portion of the page represented by the control.
The OutputDirective may be applied to the ASCX file that comprises a User Control. The OutputDirective for a User Control may also use the Shared property to tell ASP.NET to cache one version of the control for all pages that use it, resulting in potentially even higher performance over the span of many hits (the default is false).
The following exercise illustrates caching the output of a User Control.
User Controls and Output Caching
Create a simple User control for the OutputCaching project. Navigation controls are perfect for caching, so create a control that has a menu. Name the control SiteMenu.ascx. Drag a Menu control onto the User Control, as shown here:
Add some menu items, as shown in this graphic:
Add the OutputCache directive with the following parameters in the control source, like so:
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SiteMenu.ascx.cs" Inherits="SiteMenu" %> <%@ OutputCache Duration="60" VaryByParam="none" %>Create a new page in the project. Name it UseSiteMenuControl.aspx.
Drag the SiteMenu User Control onto the UseSiteMenuControl page. When ASP.NET loads and runs your Web page, ASP.NET will cache the User Control because the User Control mentions the OutputDirective.
Make sure tracing is turned on in the UseSiteMenuControl.aspx file. (That is, set the Trace="true" attribute in the Page directive.) Surf to the page. The first time you surf to the page, you'll see the following information in the control tree section of the Trace output:
Notice the entire control tree was rendered. Push the refresh key (F5 in Internet Explorer) while looking at UseSiteMenuControl.aspx. Examine the control tree portion of the Trace output again. Notice that ASP.NET uses the cached control instead of re-rendering the entire SiteMenu control.
As with other caching techniques, one of the most important strategies is to turn on output caching for those pages that are accessed frequently and expensive to generate. Also, be sure to cache only those pages that don't change frequently (otherwise, you may be better off simply not using output caching).
For example, pages full of controls that render a great deal of HTML are probably expensive. Imagine a page including a DataGrid displaying an employee directory. This is a perfect candidate for caching due to several reasons. First, a database hit (or even an in-memory cache hit) Is required. Second, a DataGrid is pretty expensive to render¡ªespecially if it needs to figure out the schema of the employee directory table on the fly. Finally, an employee directory probably doesn't change very often. By caching it once, you can avoid spending a great deal of unnecessary cycles.
A related issue here is to be careful when typing asterisks into the output caching parameters such as VaryByParam. Using VaryByParam=* tells ASP.NET to generate a new page for every single request in which any query string parameter has changed. That's almost the same as not caching altogether¡ªwith the added cost of the memory consumed by the output cache. However, this may make sense for Web sites with limited audiences where the parameter variance between requests remains limited.
In addition, be wary of how caching might affect the appearance of your page on different browsers. Much of the time, content will appear the same regardless of the browser. However, if you cache some content that depends upon a specific browser feature (such as Dynamic HTML), clients whose browsers don't understand the feature may see some very weird behavior in the browser.
Tuning the behavior of the output cache is also important. Effective caching is always a matter of balance. While you can potentially speed up your site by employing output caching, the cost is memory consumption. Using instrumentation tools can help you balance performance against cost.
Finally, User Controls often represent a prime output caching opportunity¡ªespecially if they don't change frequently. Wrapping the portion of a page that doesn't change in an output cached User Control will usually enhance the perceived performance of your application at a minimal cost because only the User Control content is cached.
Caching is a tried and true way to improve the performance of almost any system. By making frequently used content available quickly through the output cache, you can speed up the perceived performance of your application by a wide margin.
Turning on output caching in ASP.NET is a matter of including the correct directive at the top of your page. Naive use of the cache involves simply placing it on the page code and setting the Duration to some number and the VaryByParam attribute to none. However, you may also control various behaviors of the output cache by setting variables within the OutputCache directive. You may also control output caching behaviors through the HttpCachePolicy class, available through the Cache property of the Response object. ASP.NET supports cache profiles so you don't have to hard-code the caching parameters into the OutputDirective.
User Controls often represent a prime output caching opportunity¡ªespecially if they're navigation controls or some other control that doesn't change very often. By applying the OutputCache directive to the User Control, ASP.NET caches that part of the page on which it was placed.
To | Do This |
Cache a page's output | Add the OutputCache directive to the page |
Store multiple versions of a page based on varying query string parameters | Use the VaryByParam attribute of the OutputCache directive |
Store multiple versions of a page based on varying headers | Use the VaryByHeader attribute of the OutputCache directive |
Store multiple versions of a page based on varying browsers | Use the VaryByCustom attribute of the OutputCache directive, selecting ¡°browser¡± as the value |
Specify the location of the cached content | Specify the Location attribute in the OutputCache directive |
Access caching attributes programmatically | Use the Cache property of the Response object, which is an instance of the HttpCachePolicy class |
Offload output caching configuration to the Web.Config file | Add outputCacheProfile elements to your Web.Config file. Use them as necessary |
Cache a User Control | Apply the OutputCache directive to the control's ASCX file |
discuss this topic to forum
