Developer
Featured Article |
Get to Know Your Caching
Options in ASP.NET |
 |
By Scott Mauvais, MCSE, MCSD,
MCDBA
Over the past months I have covered several
topics related to performance. When it comes to Web
applications, probably the most important aspect of
performance is the proper use of caching. In fact, for most
sites, even if you do everything else wrong, you will probably
be safe if you get caching right. My point here is not to
encourage bad design that will inevitably lead to bad
performance down the road but rather to underscore the
relative impact of caching. Where most other performance
optimization techniques result in modest gains of anywhere
from a handful to a couple dozen percentage points, caching
can increase performance by an order of
magnitude.
This month I
will look at the new caching features built into Microsoft�
ASP.NET. I will start out with a review of a couple of
different approaches to caching, discuss how they impact
performance, and then discuss how these were used in Classic
ASP applications. Next, I will cover the new caching options
in ASP.NET. Along the way, I will show you some advanced
techniques that will give you fine-grained control over
caching.
|
My goal in this article is to
provide an overview of the caching options available in
ASP.NET. To understand how you can use Microsoft ASP.NET,
especially its built-in caching functionality, you should pick
up a copy of G. Andrew Duthie's new book, Microsoft
ASP.NET Step by Step, which will walk you through
everything you want to know about ASP.NET; it also includes an
entire chapter devoted to caching. To get an idea of the book,
you can review the table
of contents and even read a sample
chapter. If you plan to create server controls, you should
also read Developing
Microsoft ASP.NET Server Controls and Components by Nikhil
Kothari and Vandana Datye because there are some special
considerations you need to keep in mind when you are caching
server controls or if you want to do fragment caching. You can
also review the book's table of contents and read a sample
chapter.
|
The
Benefits of Cache |
Before I get into the specifics of caching in Microsoft
ASP.NET, I need to quickly cover the reasons for caching in
the first place and discuss the different types of caching
that are available.
When it comes to any given Web
site, almost all the information it serves up is the same.
Even for the most dynamic sites, the majority of information
on a page remains the same, with only a small portion of the
page actually changing based on the user's input or to reflect
updated data. Take a typical stock tracking site, for example.
I went to MSN Money
Central and pulled up a quote for Microsoft
with the default display. Of the 33 K of HTML�not to mention
all the images�fewer than 100 bytes (those related to the
current price and volume) change frequently, and those only
during the trading day. The rest of the page is the standard
UI that is constant across the site or changes only slowly
throughout the day (such as links to news stories and data on
fundamentals).
Granted, a stock site is the extreme
example. Most sites are not nearly as dynamic. Even for
commerce or highly personalized sites, most of the key
elements are shared across users and change only slowly. As
you know from your performance testing, it is significantly
more expensive to generate dynamic pages compared to static
ones.
The goal, of course, is to design your
application so that it can serve up dynamic content nearly as
fast as static content. For a static page the server simply
reads the file off the disk (or more likely, from the file
system cache) and streams it back to the browser. For dynamic
pages, not only does your application need to retrieve and
execute the ASP code but in most cases it needs to connect to
a back-end database or host system to generate the dynamic
content. Although executing the ASP code adds some overhead
(albeit much less in ASP.NET than it did in Classic ASP) the
real hit comes when you connect to those back-end systems. If
you can eliminate that connection to the database or host, or
better yet, save the execution of the ASP, you can achieve
performance that begins to approach that of static pages. And
that is exactly what caching enables you to do.
|
|
Types of
Cache |
Okay, you know cache is good. With Web apps, there are a
couple of different types of cache and deciding on which one
is best depends on the type of information you want to cache
and how your app will use it. Broadly speaking, you have two
options for caching: data and output. Output caching then
breaks down to page and fragment caching.
Data
caching refers to storing the raw data in an XML document
or some other data structure the first time it is requested,
and then building the markup from the cached data each time
you render the page. With output caching, you store
the actual HTML (or cHTML or WML or whatever markup you are
using) and simply return it to the browser each time it is
requested. Page and fragment caching are pretty
straightforward because they refer to caching either the
entire page or simply a fragment (say a dropdown or a list
box).
If possible, it is always better to use output
caching because this saves the overhead of generating the
actual markup for each request. The downside is that if you
display the same data in a number of formats, this can quickly
require a lot of memory. In Classic ASP you would typically
use the Application object as your cache, as
demonstrated in Listing 1.
Listing 1: Building a fragment cache in Classic
ASP
'-- Make sure application
variable is populated '-- If not, populate it. If
Application("gsProductsDairy") = "" Then Call
GetProductsDairy End If
'-- Write the cached
value Response.Write
Application("gsProductsDairy")
Sub GetProductsDairy
'-- Skip declaring vars and error
checking to make '-- sample code easier to read.
'-- Instantiate ADO objects
Set cnNorthwind =
Server.CreateObject("ADODB.Connection") Set
rsProducts =
Server.CreateObject("ADODB.Recordset")
'-- Open connection and recordset
sSQL = "SELECT P.ProductID, P.ProductName
" _
&
"FROM Products P
"
_ &
"WHERE P.CategoryID = 4 /* dairy */ "
_ &
"ORDER BY
ProductName"
cnNorthwind.Open "Northwind", "scott�,
"secret" rsProducts.Open sSQL, cnNorthwind,
_
adOpenForwardOnly, adLockReadOnly
'-- Start HTML Select tag
sOption = "<SELECT name=cboProducts>" &
Chr(13) '-- Loop through result
set Do Until rsProducts.EOF
sOption =
sOption
_ & " <OPTION
VALUE="
_ &
Cstr(rsProducts("ProductID")) & ">"
_
&
rsProducts("ProductName")
_ & "
</OPTION>" &
Chr(13)
rsProducts.MoveNext Loop
'-- End HTML SELECT tag
sOption = sOption & "</SELECT>"
'-- Update Application
variable Application.Lock
Application("gsProductsDairy") = sOption
Application.UnLock End Sub
Wow, that's a lot of code simply to cache a drop down.
Worse still, you would still need to write the code to refresh
the cache on a regular basis. And it doesn't stop there. This
is only one drop down and you would have to go though a
similar process for each UI element. Granted, you could
develop a utility function so that you didn't have to write
the exact same code a couple hundred times, but it still is
pretty ugly. Fortunately, you can achieve the same
result in Microsoft ASP.NET with a fraction of the effort.
|
ASP.NET
Gives You Cache for Free |
Like many other aspects of the Microsoft .NET platform, the
caching architecture supports both declarative and
programmatic approaches to defining what information gets
cached. The declarative model is easiest and works for most
cases, so that's what I will cover here. When you need more
control, you can be assured it is available to you using the
programmatic API. By default, ASP.NET caches at the page
level; however, you can work with fragments or the raw data if
you need more granular control.
Wait, the page level?
If you have worked with caching Web sites before, this might
seem counterintuitive. First off, page-level caching usually
was not part of the development domain. Rather, it was handled
by a chunk of hardware such as Microsoft Internet
Security and Acceleration Server or some other front-end
proxy. Second, with many proxy servers, caching complete pages
worked only when the page served up did not change. Microsoft
ISA Server properly handles dynamic pages but you might find
some other proxy server between the end user and your
application. In other words, page caching was a fine solution
for static sites; however, real developers create dynamic
sites.
The caching engine in ASP.NET is built into the
runtime, and it understands the dynamic pages so it will cache
multiple copies of a page if the input parameters (such as
query string or HTTP post variables) change. The easiest way
to understand how the caching engine in Microsoft ASP.NET
works is simply to show you how to use it. To mark a page as
cacheable, simply put the @ OutputCache directive at
the top of the page, as in this example:
<%@ OutputCache Duration="300"
Location="Any"
VaryByParam="category"%>
That's
it. I have just replaced nearly all the lines in Listing 1 (I
would still need to connect to the database at some point but
that, too, is much easier in Microsoft .NET) with a single
line. What's more, that single line is much more powerful than
all the preceding code I wrote.
While it is only a
single line, the three attributes give you a lot of
flexibility; let's look at each one.
- Duration. This one should be obvious.
The Duration attribute determines the length of time,
in seconds, the ASP.NET runtime caches the page. Once the
time period expires, the runtime automatically removes the
page from the cache and will regenerate it on the next
request. This attribute is required.
- Location. While potentially less
obvious, this attribute is equally easy to understand. The
Location attribute determines where the page can be
cached. The valid choices are Any, Client
(typically a browser), Server (the Web server
processing the request), Downstream (any HTTP 1.1
cache-capable device, such as a proxy server other than the
origin server), and None. This attribute is optional and the
default is Any. For those familiar with the HTTP protocol,
this is simply setting the Cache-Control header.
- VaryBy. This is where you get the real
power of the ASP.NET caching engine, because it allows you
to cache multiple copies of each page based on the criteria
defined. Because it is a bit more complex, I will discuss it
in more detail in the following section.
Before I discuss the VaryBy attributes (yes, there
is more than one), however, I want to point you to some of the
other caching options that I've mentioned but that are out of
the scope of this article. The first is fragment caching. For
more information on it, see the Caching
Portions of an ASP.NET Page topic in the MSDN� Online
Library or in the SDK on MSDN. To use fragment caching, you
will need to build your fragments as Web Forms user controls.
As I mentioned at the beginning of the article, a great
reference for developing controls is Nikhil Kothari's book, Developing
Microsoft ASP.NET Server Controls and Components. The
second topic is data caching. To read up on it, see the Caching
Application Data topic, which is also available in the
book on line or in the SDK on MSDN. |
|
Caching
Multiple Copies of Dynamic Pages |
The VaryBy attributes are best described by example,
so let's assume we are building a shopping site based on the
Northwind database. One of the pages lists all the products in
a given department. It doesn't really matter what the page
looks like other than that each department's page is exactly
the same, with the only variation being the actual products
listed. To get to this page, the user clicks on a URL that
looks similar to this
one: http://www.MyFirstCachingProject.com/Department.aspx?category=4&userid=ScottM
.
Remember that the caching directive is
<%@ OutputCache Duration="300"
VaryByParam="category" Location="Any�%>
The VaryByParam="category" attribute tells the
engine to cache a separate copy for each value of
category on the query string or form POST parameter.
Values such as userid are ignored. However, if my site were
personalized, I would need to cache pages based on both values
(otherwise I would be in the embarrassing situation of
displaying one user's cached data to all subsequent visitors
until the cache expired, when a new, lucky user would have
that honor�d'oh!). You can include multiple parameters by
separating them with semicolons:
VaryByParam="category;userid". If you want to vary a
page by all attributes, you can use an asterisk:
VaryByParam="*".
Besides caching pages based on
parameters, ASP.NET enables you to vary pages based on HTTP
headers, major browser versions, and even custom strings.
Headers can be used for caching localized pages
(Accept-Language and Accept-Charset) and some personalization
and membership approaches (Authorization). The use of browser
version allows you to customize your site for down-level
browsers yet still provide a rich caching infrastructure. With
custom strings, you can do whatever you want.
To vary
by header, the declaration would look like this:
<%@ OutputCache Duration="60"
VaryByParam="None"
VaryByHeader="Accept-Language"
%>
For custom:
<%@ OutputCache Duration="60"
VaryByParam="None" VaryByCustom="MySpecialString" %>
For browser:
<%@ OutputCache Duration="10"
VaryByParam="None"
VaryByCustom="browser"
%>
You probably noticed that caching version based on browser
uses the VaryByCustom attribute. This is because it is
implemented the same way that you would extend the caching
functionality to include your own custom string. You might
have also noticed that both the header and custom approaches
include VaryByParam="None". This is because you must
always have a VaryByParam attribute. If you don't want
to cache based on the input parameters, the value
"None" tells the caching runtime to ignore the
parameters when it decides which instance of the page to pull
from cache.
This is important so I'll say it again: you
must always include a VaryByParam attribute even if you
plan to cache based on some other item. Yes, I have forgotten
this a few times so I figured I would save you my pain.
|
For More
Information |
Now that you have an overview of the caching infrastructure
in ASP.NET, you know how to give a dramatic performance boost
to your Web apps. Better yet, in some cases it can be as easy
as adding a single line to the top of each Web page. For those
more complicated examples where you need to take advantage of
fragment caching, you will want to read Developing
Microsoft ASP.NET Server Controls and Components by Nikhil
Kothari.
To see exactly how easy it is to use caching
and the other features of ASP.NET, you should get a copy of Microsoft
ASP.NET Step by Step by G. Andrew Duthie. With these books
in hand, you will be ready for any Web development
challenge.
Another book you should consider picking up
is Building XML Web
Services for the Microsoft .NET Platform by Scott Short.
This book covers everything from architecture and protocols to
the best practices you need to know to build Web-ready,
distributed-object applications.
For additional books
and information on Web services, check out the Microsoft
.NET XML Web Services page.
For those of you
interested in mobile development, you will want to see my
previous article, Go
Mobile�Write Once, Access Everywhere with Microsoft Mobile
Internet Toolkit, and get a copy of Building
.NET Applications for Mobile Devices by Andy Wigley and
Peter Roxburgh.
Microsoft Press� provides in-depth
documentation for these and all the other issues related to
developing for .NET. For a complete list of .NET titles from
Microsoft Press, see the Inside
Information About Microsoft .NET page.
|
 |
 |
|
Last Updated: Thursday, September 5,
2002 |
| |
 |
 |
|