Caching Overview
Caching Overview
Caching is a technique widely used in computing to increase performance by keeping frequently accessed or expensive
data in memory. In the context of a Web application, caching is used to retain pages or data across HTTP requests and
reuse them without the expense of recreating them.
ASP.NET has three kinds of caching that can be used by Web applications:
Output caching is useful when the contents of an entire page can be cached. On a heavily accessed site, caching
frequently accessed pages for even a minute at a time can result in substantial throughput gains. While a page is cached
by the output cache, subsequent requests for that page are served from the output page without executing the code that
created it.
Sometimes it is not practical to cache an entire page - perhaps portions of the page must be created or customized for
each request. In this case, it is often worthwhile to identify objects or data that are expensive to construct and are
eligible for caching. Once these items are identified, they can be created once and then cached for some period of time.
Additionally, fragment caching can be used to cache regions of a page's output.
Choosing the time to cache an item can be an interesting decision. For some items, the data might be refreshed at
regular intervals or the data is valid for a certain amount of time. In that case, the cache items can be given an expiration
policy that causes them to be removed from the cache when they have expired. Code that accesses the cache item simply
checks for the absence of the item and recreates it, if necessary.
The ASP.NET cache supports file and cache key dependencies, allowing developers to make a cache item dependent on
an external file or another cache item. This technique can be used to invalidate items when their underlying data source
changes.
Output caching is a powerful technique that increases request/response throughput by caching the content generated
from dynamic pages. Output caching is enabled by default, but output from any given response is not cached unless
explicit action is taken to make the response cacheable.
To make a response eligible for output caching, it must have a valid expiration/validation policy and public cache visibility.
This can be done using either the low-level OutputCache API or the high-level @ OutputCache directive. When output
caching is enabled, an output cache entry is created on the first GET request to the page. Subsequent GET or HEAD
requests are served from the output cache entry until the cached request expires.
The output cache also supports variations of cached GET or POST name/value pairs.
The output cache respects the expiration and validation policies for pages. If a page is in the output cache and has been
marked with an expiration policy that indicates that the page expires 60 minutes from the time it is cached, the page is
removed from the output cache after 60 minutes. If another request is received after that time, the page code is
executed and the page can be cached again. This type of expiration policy is called absolute expiration - a page is valid
until a certain time.
The following example demonstrates a simple way to output cache responses using the @ OutputCache directive. The
example simply displays the time when the response was generated. To see output caching in action, invoke the page
and note the time at which the response was generated. Then refresh the page and note that the time has not changed,
indicating that the second response is being served from the output cache.
CS\outputcache1.aspx
<%@ OutputCache Duration="60" VaryByParam="none" %>
<html>
</script>
<body>
</body>
</html>
Of course, in the previous example, very little work is saved by output caching. The following example shows the same
technique for output caching, but queries a database and displays the results in a grid.
CS\outputcache2.aspx
<%@ OutputCache Duration="60" VaryByParam="none" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
MyDataGrid.DataSource=new DataView(ds.Tables[0]) ;
MyDataGrid.DataBind();
TimeMsg.Text = DateTime.Now.ToString("G");
}
</script>
<body>
<p>
</body>
</html>
In the final example, the application is modified slightly to allow the user to selectively query for authors in various states.
This example demonstrates caching requests varying by the name/value pairs in the query string using the
VaryByParam attribute of the @ OutputCache directive.
Note that the first time you click the link for a given state, it generates a new timestamp at the bottom of the page.
Thereafter, whenever a request for that state is resubmitted within a minute, you get the original timestamp indicating
that the request has been cached.
CS\outputcache3.aspx
<%@ OutputCache Duration="60" VaryByParam="state" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
String selectCmd;
String state = Request["state"];
MyDataGrid.DataSource=new DataView(ds.Tables[0]);
MyDataGrid.DataBind();
TimeMsg.Text = DateTime.Now.ToString("G");
}
</script>
<body>
<h3><font face="Verdana">Using the Output Cache</font></h3>
<b>Authors by State:</b>
<p>
<p>
</body>
</html>
Applications that want more control over the HTTP headers related to caching can use the functionality provided by the
System.Web.HttpCachePolicy class. The following example shows the code equivalent to the page directives used in
the previous samples.
C#
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
VB
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))
Response.Cache.SetCacheability(HttpCacheability.Public)
JSCRIPT
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
To make this a sliding expiration policy, where the expiration time out resets each time the page is requested, set the
SlidingExpiration property as shown in the following code.
C#
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetSlidingExpiration(true);
VB
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60))
Response.Cache.SetCacheability(HttpCacheability.Public)
Response.Cache.SetSlidingExpiration(True)
JSCRIPT
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetSlidingExpiration(true);
Note: When sliding expiration is enabled (SetSlidingExpiration(true)), a request made to the origin server always
generates a response. Sliding expiration is useful in scenarios where there are downstream caches that can satisfy client
requests, if the content has not expired yet, without requesting the content from the origin server.
Applications being ported from ASP may already be setting cache policy using the ASP properties; for example:
C#
Response.CacheControl = "Public";
Response.Expires = 60;
VB
Response.CacheControl = "Public"
Response.Expires = 60
JSCRIPT
Response.CacheControl = "Public";
Response.Expires = 60;
These properties are supported by ASP.NET and have the same effect as the other examples that have been shown.
Section Summary
In addition to output caching an entire page, ASP.NET provides a simple way for you to output cache regions of page
content, which is appropriately named fragment caching. You delineate regions of your page with a user control, and
mark them for caching using the @ OutputCache directive introduced in the previous section. This directive specifies the
duration (in seconds) that the output content of the user control should be cached on the server, as well as any optional
conditions by which it should be varied.
For example, the following directive instructs ASP.NET to output cache the user control for 120 seconds, and to vary the
caching using the "CategoryID" and "SelectedID" querystring or form post parameters.
The VaryByParam attribute is extremely powerful and allows user control authors to instruct ASP.NET to cache/store
multiple instances of an output cache region on the server. For example, the following URLs to the host page of the
previous user control cache separate instances of the user control content.
http://localhost/mypage.aspx?categoryid=foo&selectedid=0
http://localhost/mypage.aspx?categoryid=foo&selectedid=1
Logic within a user control can then dynamically generate different content (which is cached separately) depending on
the arguments provided.
In addition to supporting the VaryByParam attribute, fragment caching also supports a VaryByControl attribute.
Whereas the VaryByParam attribute varies cached results based on name/value pairs sent using POST or GET, the
VaryByControl attribute varies the cached fragment by controls within the user control. For example:
Note that similar to output-cached pages, explict use of VaryByParam is required even if it is not used.
If the user control contained a drop-down select box control named Category, the user control's output would vary based
on the selected value within that control.
Just as it is possible to nest user controls recursively within a page (that is, a user control declared within another server
control), it is also possible to nest output-cached user controls recursively. This provides a powerful composition model
that enables cached regions to be composed of further subcached regions.
The following sample code demonstrates how to cache two menu sections of a page using a declarative user control.
C#
<%@ Register TagPrefix="Acme" TagName="Menu" Src="Menu.ascx" %>
<html>
<body>
<table>
<tr>
<td>
<Acme:Menu Category="LeftMenu" runat=server/>
</td>
<td>
<h1>Hi, the time is now: <%=DateTime.Now%> </h1>
</td>
<td>
<Acme:Menu Category="RightMenu" runat=server/>
</td>
<tr>
</table>
</body>
</html>
VB
<%@ Register TagPrefix="Acme" TagName="Menu" Src="Menu.ascx" %>
<html>
<body>
<table>
<tr>
<td>
<Acme:Menu Category="LeftMenu" runat=server/>
</td>
<td>
<h1>Hi, the time is now: <%=Now%> </h1>
</td>
<td>
<Acme:Menu Category="RightMenu" runat=server/>
</td>
<tr>
</table>
</body>
</html>
JSCRIPT
<%@ Register TagPrefix="Acme" TagName="Menu" Src="Menu.ascx" %>
<html>
<body>
<table>
<tr>
<td>
<Acme:Menu Category="LeftMenu" runat=server/>
</td>
<td>
<h1>Hi, the time is now: <%=DateTime.Now%> </h1>
</td>
<td>
<Acme:Menu Category="RightMenu" runat=server/>
</td>
<tr>
</table>
</body>
</html>
The following sample code shows the implementation of the "Acme:Menu" user control with caching support.
C#
<%@ OutputCache Duration="120" VaryByParam="none" %>
</script>
</script>
Note that this example output caches the response of each user control for a period of 120 seconds. All logic necessary to
recreate each menu user control in the event of a cache miss (either because 120 seconds has expired or because
memory conditions on the server have become scarce) is encapsulated cleanly within the user control.
The following example shows simple fragment caching. The sample caches the output from a control that retrieves data
from an SQL Server database, while keeping the dynamic properties of the parent page. You can see that the page is
dynamic because the time is updated with every refresh, while the control is only updated every 60 seconds.
CS\fragmentcache1.aspx
<%@ Register TagPrefix="Acme" TagName="DataControl" Src="datactrl.ascx"
%>
<html>
TimeMsg.Text = DateTime.Now.ToString("G");
}
</script>
<body>
<Acme:DataControl runat="server"/>
<br>
</body>
</html>
CS\datactrl.ascx
<%@ OutputCache Duration="60" VaryByParam="none" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
MyDataGrid.DataSource=new DataView(ds.Tables[0]) ;
MyDataGrid.DataBind();
TimeMsg.Text = DateTime.Now.ToString("G");
}
</script>
<p>
Caveats
Note: Attempts to programmatically manipulate an output-cached control from its containing page result in an error. For
example, attempts to use a declarative data binding expression on the user control tag generates parser errors, as shown
in the following code.
The reason for this is simple. In cases when the content of a user control is output cached, an instance of the control is
created only on the first request; thus, once cached, the control is no longer available. Instead, you should encapsulate
all the logic necessary to create the content of a user control directly within the control itself; this is typically done within
the user control's Page_Load event or Page_PreRender event.
You can declare and use other declarative property parameters to customize the control. For example, the previous user
control can be customized as follows:
These declarations cause the appropriate code to be generated and executed by the page compiler in the event that the
control is created as a result of a cache miss. User control developers can then access these settings just as they would in
a non-cached user control scenario.
Section Summary
1. In addition to output caching an entire page, ASP.NET provides a simple way for you to output cache regions of
page content, which is appropriately named fragment caching.
2. You delineate regions of your page with a user control and mark them for caching using the @ OutputCache
directive introduced in the previous section.
3. Just as it is possible to nest user controls recursively within a page (that is, a user control declared within
another server control), it is also possible to nest output-cached user controls recursively.
4. Attempts to programmatically manipulate an output-cached control from its containing page result in an error.
Instead, you should encapsulate all the logic necessary to create the content of a user control directly within
the control itself, typically within the user control's Page_Load event or Page_PreRender event.
5. It is possible to declare and use other declarative property parameters to customize the control.
ASP.NET provides a full-featured cache engine that can be used by pages to store and retrieve arbitrary objects across
HTTP requests. The ASP.NET cache is private to each application and stores objects in memory. The lifetime of the cache
is equivalent to the lifetime of the application; that is, when the application is restarted, the cache is recreated.
The cache provides a simple dictionary interface that allows programmers to easily place objects in and retrieve them
from the cache. In the simplest case, placing an item in the cache is just like adding an item to a dictionary:
C#
Cache["mykey"] = myValue;
VB
Cache("mykey") = myValue
JSCRIPT
Cache("mykey") = myValue;
For applications that need more sophisticated functionality, the ASP.NET cache supports scavenging, expiration, and file
and key dependencies.
Scavenging means that the cache attempts to remove infrequently used or unimportant items if memory
becomes scarce. Programmers who want to control how scavenging occurs can provide hints to the scavenger
when items are inserted into the cache that indicate the relative cost of creating the item and the relative rate
at which the item must be accessed to remain useful.
Expiration allows programmers to give cache items lifetimes, which can be explicit (for example, expire at 6:00)
or can be relative to an item's last use (for example, expire 20 minutes after the item was last accessed). After
an item has expired, it is removed from the cache and future attempts to retrieve it return the null value unless
the item is reinserted into the cache.
File and key dependencies allow the validity of a cache item to be based on an external file or on another cache
item. If a dependency changes, the cache item is invalidated and removed from the cache. For an example of
how you might use this functionality, consider the following scenario: an application reads financial information
from an XML file that is periodically updated. The application processes the data in the file and creates a graph
of objects that represent that data in a consumable format. The application caches that data and inserts a
dependency on the file from which the data was read. When the file is updated, the data is removed from the
cache and the application can reread it and reinsert the updated copy of the data.
The following sample shows a simple use of the cache. It executes a database query and caches the result, which it
continues to use for the lifetime of the application. When you run the sample, note the message at the bottom of the
page. When first requested, it indicates that the data was explicitly retrieved from the database server. After refreshing
the page, the page notes that the cached copy was used.
CS\datacache1.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
DataView Source;
Source = (DataView)Cache["MyDataSet"];
if (Source == null) {
MyDataGrid.DataSource=Source;
MyDataGrid.DataBind();
}
</script>
<body>
<p>
</form>
</body>
</html>
The next example shows a cache item that depends on an XML file. It is similar to the first example, although in this case
the data is retrieved from an XML data source instead of a database server. When the data is cached, the XML file is
added as a dependency.
When a new record is added using the form at the bottom of the page, the XML file is updated and the cached item must
be recreated.
CS\datacache2.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<html>
if(!IsPostBack) {
LoadData();
}
}
// append a row
try {
DataRow newAuthor = ds.Tables[0].NewRow();
newAuthor["au_id"] = AuthorId.Text;
newAuthor["au_lname"] = LastName.Text;
newAuthor["au_fname"] = FirstName.Text;
newAuthor["phone"] = Phone.Text;
newAuthor["address"] = Address.Text;
newAuthor["city"] = City.Text;
newAuthor["state"] = State.Text;
newAuthor["zip"] = PostalCode.Text;
newAuthor["contract"] = Contract.Checked;
ds.Tables[0].Rows.Add(newAuthor);
} catch(Exception) {
CacheMsg.Text = "Failed to create author with id = (" +
AuthorId.Text + ")<br>" + "Author already exists.";
return;
}
Cache.Remove("MyData");
LoadData();
}
LoadData();
}
void LoadData() {
if(Source == null) {
// read the data from the XML source
DataSet ds = new DataSet();
MyDataGrid.DataSource = Source;
MyDataGrid.DataBind();
}
</script>
<body>
<form runat="server">
<hr>
<p>
<table>
<tr>
<td>Author Id:</td>
<td><ASP:TextBox id=AuthorId Text="111-11-1111"
runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="AuthorId"
Display="Static" ErrorMessage="*" runat=server/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><ASP:TextBox id=LastName Text="Doe" runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="LastName"
Display="Static" ErrorMessage="*" runat=server/></td>
</tr>
<tr>
<td>First Name:</td>
<td><ASP:TextBox id=FirstName Text="John" runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="FirstName"
Display="Static" ErrorMessage="*" runat=server/></td>
</tr>
<tr>
<td>Phone:</td>
<td><ASP:TextBox id=Phone Text="555 555-5050"
runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="Phone"
Display="Static" ErrorMessage="*" runat=server/></td>
</tr>
<tr>
<td>Address:</td>
<td><ASP:TextBox id=Address Text="One Microsoft Way"
runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="Address"
ErrorMessage="*" Display="Static" runat=server/></td>
</tr>
<tr>
<td>City:</td>
<td><ASP:TextBox id=City Text="Redmond" runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="City"
ErrorMessage="*" Display="Static" runat=server/></td>
</tr>
<tr>
<td>State:</td>
<td><ASP:TextBox id=State Text="WA" runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="State"
ErrorMessage="*" Display="Static" runat=server/></td>
</tr>
<tr>
<td>Postal Code:</td>
<td><ASP:TextBox id=PostalCode Text="98052"
runat=server/></td>
<td><ASP:RequiredFieldValidator ControlToValidate="PostalCode"
ErrorMessage="*" Display="Static" runat=server/></td>
</tr>
<tr>
<td>Contract:</td>
<td><ASP:CheckBox id=Contract Checked runat="server"/></td>
<td></td>
</tr>
</table>
<p>
<hr>
<p>
</form>
</body>
</html>
Note that a file dependency is added by using Cache.Insert and supplying a CacheDependency object referencing the
XML file.
Cache.Insert("MyData", Source,
new CacheDependency(Server.MapPath("authors.xml")));
A cache item can depend on a single or multiple files or keys. As mentioned previously, an application can also set
expiration policy on a cache item. The following code sets an absolute cache expiration time.
The relevant parameter is the call to DateTime.Now.AddHours(1), which indicates that the item expires 1 hour from
the time it is inserted. The final argument, TimeSpan.Zero indicates that there is no relative expiration policy on this
item.
The following code shows how to set a relative expiration policy. It inserts an item that expires 20 minutes after it is last
accessed. Note the use of DateTime.MaxValue, which indicates that there is no absolute expiration policy on this item.
Section Summary