Output Caching






Output Caching

The idea behind output caching is that if an ASP.NET page hasn't changed, why go through the process of recompilation and execution? Why not, after the page has been run for the first time, just store the HTML it generated, and when the page is requested again, just return the HTML? Less processing on the server means more pages can be served, and those people requesting cached pages get them quicker.

Of course, the issue you have is that pages are often dynamic; they contain server controls, user controls, and data, and maybe some part of the page changes with every request, meaning if it was cached, users would always see the same page they saw after the first request. A similar issue comes into play when pages contain data from a database, where you can cache the page, but what happens if the data changes? The users would see stale data.

All of these problems are solved by the ASP.NET caching framework, along with SQL Server (both 2000 and 2005) to help with the data side of things. The downside of caching is that it consumes memory, since the cache is memory-based, but a Least Recently Used (LRU) algorithm is used, meaning that items in the cache that are accessed infrequently can be removed from the cache. This ensures that the cache does not consume more memory than necessary.

The caching framework is flexible and has the notion of cache dependencies, where items in the cache can be dependent upon external conditions; when the conditions change, items can be removed from the cache. Items in the cache can be dependent upon:

  • A time, so that after a certain time (either fixed or sliding) they can be removed from the cache

  • A file, so if the file changes, the item can be removed from the cache

  • A key, so if another item in the cache changes, the item can be removed from the cache

  • A data query, so that if the underlying data the page is dependent upon changes, the item can be removed from the cache

Which method you use depends upon your requirements, but as a general rule, caching can bring huge improvements in performance and scalability.

Configuring Output Caching

The simplest caching solution is output caching, where the output of the page, or user control, is cached. The page output is the HTML that is sent back to the user, hence the term "output caching." Caching page output can be easily enabled by use of the OutputCache directive at the top of the page or user control:

<%@ OutputCache %>

The attributes that can be added to this directive are shown in Figure.

Attributes of the OutputCache Directive

Attribute

Description

CacheProfile

The page is cached depending upon the settings defined in the outputCacheSettings section of web.config. This attribute is not supported for user controls.

Duration

The time, in seconds, that the page is cached for. Once this time is exceeded, the page is evicted from the cache.

Location

The location where caching takes place. Can be one of the following OutputCacheLocation values:

Any, indicating the output cache can be on the client browser, a proxy server, or on the ASP.NET server.

Client, indicating the cache can be located on the client browser.

Downstream, indicating the cache can be located on any HTTP 1.1 cache-capable server.

None, indicating the output cache is disabled for the page.

Server, indicating the cache is located on the ASP.NET server processing the request.

ServerAndClient, indicating the cache can be located only at the client browser or the ASP.NET server.

NoStore

Indicates whether or not secondary cache stores cache sensitive information. This attribute is not supported for user controls.

Shared

Indicates whether or not user control output can be shared across multiple pages. The default is False.

SqlDependency

When using SQL Server 2005, a value of CommandNotification indicates that caching takes place until a notification is received from SQL Server 2005 indicating that the data set the page is based upon has changed. When using SQL Server 2000, this attribute contains a set of database and table names, indicating that caching takes place until any data in the named tables changes.

VaryByControl

The page is cached depending upon a semicolon-separated list of control IDs representing the values upon which to cache. This attribute is only supported in user controls, and is required unless VaryByParam is used.

VaryByCustom

Indicates that the page is cached depending upon custom requirements. The custom requirements can be implemented by overriding the HttpApplication.GetVaryByCustomString method in the global.asax file. A variation of custom requirements is if the string browser is used, in which case caching is depending upon the browser name and major version.

VaryByHeader

The page is cached depending upon a semicolon-separated list of HTTP headers. This affects HTTP 1.1 cache locations. This attribute is not supported in user controls.

VaryByParam

The page is cached according to a semicolon-separated list of query string or post values. This attribute is required, and to ignore any values use an empty string, or a * to cache on all values.


For example, consider a grid that shows categories, each of which has a link to another page to show more of the products for that category, perhaps identified with a link such as:

<asp:TemplateField>
  <ItemTemplate>
    <a href='<%#Eval("CategoryID",
       "ViewProducts.aspx?CategoryID={0}")%>'
       target="_blank">View Products</a>
  </ItemTemplate>
</asp:TemplateField>

The ViewProductsl.aspx page could cache its output depending upon the CategoryID passed in, so that multiple requests for the same product would be served from the cache:

<%@ Page Language = "C#" ... %>
<%@ %@ OutputCache Duration = "30" VaryByParam="CategoryID" %>

The first time the page is processed, the output is cached. Subsequent requests with the same CategoryID would be served from the cache, but a different CategoryID would result in the page being rerun and the output also being cached. Now, there would be two items in the cache, and more would be added as different categories were viewed. You can easily test caching by adding a date and time at the top of the page, perhaps with the following line of code:

<% =DateTime.Now %>

Since this would only be executed once, when the page is first processed, subsequent requests for the page (such as simply refreshing the browser) would show the same time. If you wait until the duration is up, the page will be evicted from the cache, and the next request will reprocess the page, resulting in a new date and time.

One important point to note is that the more items you have in the cache, the more memory your Web server uses and the less memory is available for dynamic use, such as for surges in requests. While caching can improve performance, you have to balance that performance with the additional resources it requires.

Caching Portions of a Page

The problem with caching is that the entire contents of the page are cached. There are, however, circumstances when you would like to only cache portions of a page, and there are two ways in which you can achieve this. You can use Control caching, or fragment caching, as it is sometimes called, to cache portions of the page, or you can use post-cache substitution to cache the entire page but have portions of it dynamic upon each request.

Control Caching

You implement control caching by placing the portions of the page you wish to be cached into user controls. This allows you to help with the balance between caching and resource usage, because those parts of the page that are intensive to generate, such as data-bound grids or Web Servicebased data, can be cached, while the remaining server controls and HTML can be generated each time. You exploit user controls for this by simply removing those portions of a page that you wish to cache and placing them into a user control. You can then add the OutputCache attribute to the user control, as shown in Listing 6.6.

A Cached User Control

<%@ Control Language="C#" AutoEventWireup="true"
    CodeFile="CachedUserControl.ascx.cs"
    Inherits="ch05_CachedUserControl" %>
<%@ OutputCache Duration="30" VaryByParam="none" %>

<h2>Cached User Control</h2>
<p>
  This user control was cached at <% =DateTime.Now %>
</p>

Although there is no real content in this user control, it works as a simple case to show caching. The control has the OutputCache directive, and the content is simply some text with the current date and time. Consider another user control, shown in Listing 6.7, which isn't cached, but also shows the current date and time.

A Non-Cached User Control

<%@ Control Language="C#" AutoEventWireup="true"
    CodeFile="NonCachedUserControl.ascx.cs"
    Inherits="ch05_NonCachedUserControl" %>

<h2>Non-cached User Control</h2>
<p>
  This user control was not cached at <% =DateTime.Now %>
</p>

For these to work, the user controls simply need to be included on a page, as shown in Listing 6.8.

The result of the control caching is shown in Figure.

1. Control caching in action


You can see that the date on the page heading is the same as for the noncached control, while the cached user control shows a different datethe date the page was first requested. Refresh was pressed a few times, showing that the control is cached, but not the rest of the contents. In the examples just presented, the page and control cache durations were the same, but there is no requirement for this to be the case; different values are perfectly acceptable depending upon your caching requirements.

Using Cached and Non-Cached User Controls

<%Page Language ="C#" AutoEventWireup="true"
    CodeFile="FragmentCaching.aspx.cs"
    Inherits="ch05_FragmentCaching" Title="Untitled Page" %>

<%Register Src="CachedUserControl.ascx"
    TagName="CachedUserControl" TagPrefix="uc1" %>
<%Register Src="NonCachedUserControl.ascx"
    TagName="NonCachedUserControl" TagPrefix="uc2" %>

<html>
<form runat="server">

  <h1>
    Page generated at <% = DateTime.Now %></h1>
  <uc1:CachedUserControl id="CachedUserControl1" runat="server" />

  <br /><br />

  <uc2:NonCachedUserControl ID="NonCachedUserControl1"
    runat="server" />

</form>
</html>

Post-Cache Substitution

For the opposite situation, you can use post-cache substitution, where most of the page is cached but some portions aren't. A good example of this is advertisements, where each page request should show a new advertisement, even if the rest of the page is cached. This is in fact what the AdRotator control does.

Post-cache substitution is designed for use at the control level, where the control decides that its content should not be cached. It works like this: The control implements a dynamic rendering function in a callback that is registered with the response. The cached response keeps a marker to the content, which is replaced with your real content once the item is fetched from the cache and before it is sent to the client. This type of control caching is outside the scope of general caching techniques; for more information, see Nikhil Kothari's Weblog at http://www.nikhil.net/ -. Nikhil is a member of the ASP.NET team and has written a helper class that control developers can use to aid in post-cache substitution.

Disk Caching

We've mentioned that caching is always a compromise between speed of pages returned and resource usage of the server. Another form of caching is to cache the page output to disk, which reduces the memory overhead on the server. The trade-off here is that you don't have to go through the page regeneration, but you do still have file access.

Disk-based caching is not built in to ASP.NET 2.0, but there is a solution written by one of the members of the ASP.NET team that can improve performance if your request sizes are large. With memory-based caching, large requests would consume lots of memory, so as request size increases, disk caching becomes more viable.

Another great reason for disk caching is that the cache persists across restarts, which can reduce the time for pages to initially load.

Read more about disk caching on Dmitry's Weblog at http://blogs.msdn.com/dmitryr.

Configuring Caching

As well as configuring caching at the page level, you can also configure it globally in web.config, as shown in Listing 6.9, which details three sections. There is also a fourth section, SqlCacheDependency, which is covered in the section Configuring SQL Server 2000 Cache Invalidation in ASP.NET.

Cache Configuration

<caching>
  <cache
    disableExpiration="[true|false]"
    disableMemoryCollection ="[true|false]"
    percentagePhysicalMemoryUsedLimit="Integer"
    privateBytesLimit="Integer"
    privateBytesPollTime="Integer"
    />
  <outputCache
    enableFragmentCache="[true|false]"
    enableOutputCache="[true|false]"
    omitVaryStar="[true|false]"
    sendCacheControlHeader="[true|false]"
    />
  <outputCacheSettings>
    <outputCacheProfiles>
      <clear />
      <remove name="String" />
      <add enabled="[true|false]"
        name="String"
        duration="Integer"
        varyByControl="String"
        varyByHeader="String"
        sqlDependency="String"/>
    </outputCacheProfiles>
  </outputCacheSettings>
</caching>

The details for each of the elements and attributes are shown in Figure, Figure, and Figure respectively.

Attributes for the Cache Element

Attribute

Description

disableMemoryCollection

When set to TRue, disables the collection of cache memory. The default value is false.

disableExpiration

When set to true, disables the expiration of items in the cache when memory pressure would normally expire them. The default value is false.

percentagePhysicalMemoryUsedLimit

Defines the maximum percentage of a machine's memory that will be used before expired items are flushed from the cache. The percentage includes the cache memory as well as application memory. The default value is 0, which allows ASP.NET to hueristically decide the value.

privateBytesLimit

Defines the maximum limit of an application's private memory before items are flushed from the cache. The default value is 0, which allows ASP.NET to hueristically decide the value.

privateBytesPollTime

Defines the time interval, as a TimeSpan, between polling for the applicaton's private memory usage.


Attributes for the OutputCache Element

Attribute

Description

enableFragmentCache

Indicates whether or not fragment caching is enabled. The default value is true.

enableOutputCache

Indicates whether or not output caching is enabled. When disabled, the OutputCache page directive is ignored, and cache-control headers are added to the response to indicate that upstream proxies and clients should to cache the output. The default value is true.

omitVaryStar

Indicates whether or not an HTTP "Vary: *" header is added to the response. The default value is false.

sendCacheControlHeader

Indicates whether or not the "cache-control:private" header is added to the response. The default value is false.


Attributes for the OutputCacheSettings Element

Attribute

Description

duration

Defines the amount of time in seconds that a page is stored in the cache.

enabled

Indicates whether or not the output cache is enabled for this profile.

location

Defines where the cached output can be stored. Can be one of: Any, Client, Downstream, None, Server, ServerAndClient.

name

Defines the name of the profile.

noStore

Indicates whether or not the "Cache-control: no-store" header is added to the response.

sqlDependency

Defines the SQL Dependency for the cache profile. SQL Dependencies are covered in the Data Caching section.

varyByControl

Defines the ID of the user control, or a semicolon-separated list containing IDs of multiple user controls, that is to be cached.

varyByHeader

Defines the semicolon-separated list of HTTP headers used to vary the cached output.

varyByCustom

Defines the custom function used to vary the cached output.

varyByParam

Defines the semicolon-separated list of parameters used to vary the cached output.


The cache section allows configuration of memory limits for caching, while the outputCache section allows configuration of output cache settings. Cache profiles are useful if you have common caching configurations that are used in multiple pages. To avoid having to modify every page with the same settings, you can configure a cache profile and use the CacheProfile attribute of the OutputCache page directive.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows