microsoft press home   All Products  |   Support  |   Search  |   microsoft.com Home  
 
  Microsoft Press Home  |   Register Books  |   Site Index  |   All Books  |

 

Advanced Search
Hot topics:
Books about:
Books for:
Products for:



Developer Featured Article
Get to Know Your Caching Options in ASP.NET
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.

 

Top of Page
 
Last Updated: Thursday, September 5, 2002