With
output caching, the final rendered HTML of the page is cached. When the
same page is requested again, the control objects are not created, the
page life cycle doesn't start, and none of your code executes. Instead,
the cached HTML is served. Clearly, output caching gets the theoretical
maximum performance increase, because all the overhead of your code is
sidestepped.
To see output caching in action, you can create a simple page that displays the current time of day. Figure 1 shows this page.
The code for this task is elementary:
Public Partial Class OutputCaching
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
lblDate.Text = "The time is now:<br />"
lblDate.Text &= DateTime.Now.ToString()
End Sub
End Class
You can cache an ASP.NET page
in two ways. The most common approach is to insert the OutputCache
directive at the top of your .aspx file, just below the Page directive,
as shown here:
<%@ OutputCache Duration="20" VaryByParam="None" %>
The Duration attribute instructs
ASP.NET to cache the page for 20 seconds. The VaryByParam attribute is
also required—but you'll learn about its effect later in the "Caching and the Query String" section.
When you run the test
page, you'll discover some interesting behavior. The first time you
access the page, you will see the current time displayed. If you refresh
the page a short time later, however, the page will not be updated.
Instead, ASP.NET will automatically send the cached HTML output to you,
until it expires in 20 seconds. If ASP.NET receives a request after the
cached page has expired, ASP.NET will run the page code again, generate a
new cached copy of the HTML output, and use that for the next 20
seconds.
Twenty seconds may seem like a
trivial amount of time, but in a high-volume site, it can make a
dramatic difference. For example, you might cache a page that provides a
list of products from a catalog. By caching the page for 20 seconds,
you limit database access for this page to three operations per minute.
Without caching, the page will try to connect to the database once for
each client and could easily make dozens of requests in the course of 20
seconds.
Of course, just because
you request that a page should be stored for 20 seconds doesn't mean
that it actually will be. The page could be evicted from the cache early
if the system finds that memory is becoming scarce. This allows you to
use caching freely, without worrying too much about hampering your
application by using up vital memory.
When you recompile a cached
page, ASP.NET will automatically remove the page from the cache. This
prevents problems where a page isn't properly updated, because the
older, cached version is being used. However, you might still want to
disable caching while testing your application. Otherwise, you may have
trouble using variable watches, breakpoints, and other debugging
techniques, because your code will not be executed if a cached copy of
the page is available.
|
|
1. Caching on the Client Side
Another option is to
cache the page exclusively on the client side. In this case, the browser
stores a copy and will automatically use this page if the client
browses back to the page or retypes the page's URL. However, if the user
clicks the Refresh button, the cached copy will be abandoned, and the
page will be rerequested from the server, which will run the appropriate
page code once again. You can cache a page on the client side using the
Location attribute in the OutputCache directive, which specifies a
value from the System.Web.UI.OutputCacheLocation enumeration, as shown
here:
<%@ OutputCache Duration="20" VaryByParam="None" Location="Client" %>
Client-side caching is less
common than server-side caching. Because the page is still re-created
for every separate user, it won't reduce code execution or database
access nearly as dramatically as server-side caching (which shares a
single cached copy among all users). However, client-side caching can be
a useful technique if your cached page uses some sort of personalized
data. For example, imagine a page that displays a user-specific
greeting. In this situation, server-side caching isn't ideal. The
problem is that the page will be created just once and reused for all
clients, ensuring that most will receive the wrong greeting. In this
situation, you can either use fragment caching to cache the generic
portion of the page or use client-side caching to store a user-specific
version on each client's computer.
2. Caching and the Query String
One of the main
considerations in caching is deciding when a page can be reused and when
information must be accurate up to the latest second. Developers, with
their love of instant gratification (and lack of patience), generally
tend to overemphasize the importance of real-time information. You can
usually use caching to efficiently reuse slightly stale data without a
problem and with a considerable performance improvement.
Of course, sometimes information
needs to be dynamic. One example is if the page uses information from
the current user's session to tailor the user interface. In this case,
full page caching just isn't appropriate, because the same page can't be
reused for requests from different users (although fragment caching may
help). Another example is if the page is receiving information from
another page through the query string. In this case, the page is too
dynamic to cache—or is it?
The current example
sets the VaryByParam attribute on the OutputCache directive to None,
which effectively tells ASP.NET that you need to store only one copy of
the cached page, which is suitable for all scenarios. If the request for
this page adds query string arguments to the URL, it makes no
difference—ASP.NET will always reuse the same output until it expires.
You can test this by adding a query string parameter manually in the
browser window. For example, try tacking ?a=b on to the end of your URL.
The cached output is still used.
Based on this experiment,
you might assume that output caching isn't suitable for pages that use
query string arguments. But ASP.NET actually provides another option.
You can set the VaryByParam attribute to * to indicate that the page
uses the query string and to instruct ASP.NET to cache separate copies
of the page for different query string arguments:
<%@ OutputCache Duration="20" VaryByParam="*" %>
Now when you request
the page with additional query string information, ASP.NET will examine
the query string. If the string matches a previous request and a cached
copy of that page exists, it will be reused. Otherwise, a new copy of
the page will be created and cached separately.
To get a better idea of how this process works, consider the following series of requests:
You request a page without any query string parameter and receive page copy A.
You request the page with the parameter ProductID=1. You receive page copy B.
Another user requests the page with the parameter ProductID=2. That user receives copy C.
Another user requests the page with ProductID=1. If the cached output B has not expired, it's sent to the user.
The user then requests the page with no query string parameters. If copy A has not expired, it's sent from the cache.
You can try this on your own,
although you might want to lengthen the amount of time that the cached
page is retained to make it easier to test.
NOTE
Output caching works well if
the pages depend only on server-side data (for example, the data in a
database) and the data in the query string. However, output caching
doesn't work if the page output depends on user-specific information
such as session data or cookies, because there's no way to vary caching
based on these criteria. Output caching also won't work with dynamic
pages that change their content in response to control events. In these
situations, use fragment caching instead to cache a portion of the page,
or use data caching to cache specific information.
3. Caching with Specific Query String Parameters
Setting VaryByParam to
the wildcard asterisk (*) is unnecessarily vague. It's usually better to
specifically identify an important query string variable by name.
Here's an example:
<%@ OutputCache Duration="20" VaryByParam="ProductID" %>
In this case, ASP.NET will
examine the query string, looking for the ProductID parameter. Requests
with different ProductID parameters will be cached separately, but all
other parameters will be ignored. This is particularly useful if the
page may be passed additional query string information that it doesn't
use. ASP.NET has no way to distinguish the "important" query string
parameters without your help.
You can specify several parameters as long as you separate them with semicolons:
<%@ OutputCache Duration="20" VaryByParam="ProductID;CurrencyType" %>
In this case, ASP.NET will cache separate versions, provided the query string differs by ProductID or CurrencyType.
4. A Multiple Caching Example
The following example
uses two web pages to demonstrate how multiple versions of a web page
can be cached separately. The first page, QueryStringSender.aspx, isn't
cached. It provides three buttons, as shown in Figure 2.
A single event handler
handles the Click event for all three buttons. The event handler
navigates to the QueryStringRecipient.aspx page and adds a Version
parameter to the query string to indicate which button was
clicked—cmdNormal, cmdLarge, or cmdSmall.
Protected Sub cmdVersion_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles cmdNormal.Click, cmdLarge.Click, cmdSmall.Click
Response.Redirect("QueryStringRecipient.aspx" & _
"?Version=" & CType(sender, Control).ID)
End Sub
The
QueryStringRecipient.aspx destination page displays the familiar date
message. The page uses an OutputCache directive that looks for a single
query string parameter (named Version):
<%@ OutputCache Duration="60" VaryByParam="Version" %>
In other words, this
has three separately maintained HTML outputs: one where Version equals
cmdSmall, one where Version equals cmdLarge, and one where Version
equals cmdNormal.
Although it isn't
necessary for this example, the Page.Load event handler in
QueryRecipient.aspx tailors the page by changing the font size of the
label accordingly. This makes it easy to distinguish the three versions
of the page and verify that the caching is working as expected.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
lblDate.Text = "The time is now:<br />" & DateTime.Now.ToString()
Select Case Request.QueryString("Version")
Case "cmdLarge"
lblDate.Font.Size = FontUnit.XLarge
Case "cmdNormal"
lblDate.Font.Size = FontUnit.Large
Case "cmdSmall"
lblDate.Font.Size = FontUnit.Small
End Select
End Sub
Figure 3 shows one of the cached outputs for this page.
5. Fragment Caching
In some cases, you may find
that you can't cache an entire page, but you would still like to cache a
portion that is expensive to create and doesn't vary frequently (like a
list of categories in a product catalog). One way to implement this
sort of scenario is to use data caching to store just the underlying
information used for the page. You'll examine this technique in the next
section. Another option is to use fragment caching.
To implement fragment caching,
you need to create a user control for the portion of the page you want
to cache. You can then add the OutputCache directive to the user
control. The result is that the page will not be cached, but the user
control will.
Fragment caching is
conceptually the same as page caching. It has only one catch—if your
page retrieves a cached version of a user control, it cannot interact
with it in code. For example, if your user control provides properties,
your web page code cannot modify or access these properties. When the
cached version of the user control is used, a block of HTML is simply
inserted into the page. The corresponding user control object is not
available. To avoid potential problems in your code, don't attempt to
interact with it in your code or check that it's not a null reference
(Nothing) before you do.
6. Cache Profiles
One problem with output
caching is that you need to embed the instruction into the page—either
in the .aspx markup portion or in the code of the class. Although the
first option (using the OutputCache directive) is relatively clean, it
still produces management problems if you create dozens of cached pages.
If you want to change the caching for all these pages (for example,
moving the caching duration from 30 to 60 seconds), you need to modify
every page. ASP.NET also needs to recompile these pages.
ASP.NET includes a feature called cache profiles
that makes it easy to apply the same caching settings to a group of
pages. With cache profiles, you define a group of caching settings in
the web.config file, associate a name with these settings, and then
apply these settings to multiple pages using the name. That way, you
have the freedom to modify all the linked pages at once simply by
changing the caching profile in the web.config file.
To define a cache profile,
you use the <add> tag in the <outputCacheProfiles> section,
as follows. You assign a name and a duration.
<configuration>
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="ProductItemCacheProfile" duration="60" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
...
</system.web>
</configuration>
You can now use this profile in a page through the CacheProfile attribute:
<%@ OutputCache CacheProfile="ProductItemCacheProfile" VaryByParam="None" %>
Interestingly, if you want to
apply other caching details, such as the VaryByParam behavior, you can
set it either as an attribute in the OutputCache directive or as an
attribute of the <add> tag for the profile. Just make sure you
start with a lowercase letter if you use the <add> tag, because
the property names are camel case, as are all configuration settings,
and case is important in XML.