Output Cache APIs


The page OutputCache directive, as stated earlier, should address 95 percent of your page caching needs. For the other 5 percent, the page output Cache API is used. The page output Cache API is incredibly powerful, albeit a bit more complex, and is surfaced through the HttpCachePolicy class in the System.Web.dll assembly in the System.Web namespace. An instance of this class is exposed as the Cache property on the Response class within the Page, that is, as Response.Cache.

Obviously the page output Cache API supports all the same capabilities offered by the page OutputCache directive. However, how these features are used is distinctly different. As demonstrated earlier, the OutputCache page directive code is equivalent to the output Cache API code. The output Cache API still requires you set an expiration (Duration in the OutputCache directive) for the time when the page is to be removed from the cache.

Tip

Do not use the page OutputCache directive on a page that also uses the output Cache APIs. If used together, the more restrictive setting is applied. Thus, if the page OutputCache directive has a duration of 60 seconds but the output Cache API sets a duration of 30 seconds, the page will be cached for only 30 seconds. (The same is true of the other settings as well.)

Setting the Expiration

The page OutputCache directive allows us to set a Duration attribute to control how long the page’s response can be stored in the cache before being expired. However, in the page OutputCache directive, this is always a sliding expiration—that is, the response expires from the time the page was requested plus the time in seconds specified in the Duration attribute. For example, if the Duration is 120 and the request for the page occurs at 11:29:03 PM, the page would expire at 11:31:03.

Our customers often request to have the output cached page expire at a fixed point in time. Although this is not possible using the page OutputCache directive, you can create a fixed expiration using the output Cache API.

SetExpires Method

The output Cache API exposes a single method for controlling how long a document is to be cached: SetExpires. (Note that the value set in the SetExpires method directly affects the value of the Expires HTTP header generated with the page.) The SetExpires method accepts a single parameter of type DateTime and returns void. To mirror a behavior similar to the Duration attribute in the OutputCache directive, we simply need to set this value using the DateTime class:

Response.Cache.SetExpires(DateTime.Now.AddSeconds(120));  

This follows the same sliding expiration programming model that is the default for the page OutputCache Duration attribute. The document will expire at the current time plus 120 seconds.

We can additionally specify a fixed point in time, for example, invalidating the cached response at midnight:

Response.Cache.SetExpires(DateTime.Parse("12:00:00AM"));  

This will instruct the cache to purge the response from the cache at exactly midnight. It doesn’t matter whether the document is first requested at 8:00 AM or 11:59:59 PM, the cached page is guaranteed to be evicted at precisely the time specified.

Tip

Sliding expiration is usually the recommended approach simply because setting all the pages to expire simultaneously, such as at midnight, would cause the server to re-execute all those pages at midnight, potentially putting an unnecessary load on the server.

Setting the Cacheability

The output Cache API also supports a way to configure how the output cached page behaves for downstream proxies or browsers that desire to cache the output. The SetCacheability method accepts a single parameter of type HttpCacheability. HttpCacheability is an enumeration that supports the values in Table 6-3.

Table 6-3: HttpCacheability Values

Member Name

Description

NoCache

Sets the HTTP Cache-Control header to no-cache header and indicates that only the server is allowed to cache a copy of the page. This is equivalent to Location="none" using the OutputCache directive.

Private

Sets the HTTP Cache-Control header to Private. Indicates that only the client is allowed to cache the page in its browser cache. This is equivalent to Location="Client" using the OutputCache directive.

Public

Sets the HTTP Cache-Control header to Public. Indicates that downstream clients, such as browsers or proxy caches, can cache the document, but the document is not cached by the server. This is equivalent to Location="Downstream" using the OutputCache directive. If not set, Public is the default.

Server

Sets the HTTP Cache-Control header to Server. Indicates that the response is cached only by the server and no downstream caching clients or proxies can cache the response. This is equivalent to Location="Server" using the OutputCache directive.

ServerAndNoCache

Sets the Cache-Control header to no-cache but allows the document to be cached on the server. The goal of this is to still allow programmatic output caching while sending the HTTP header Expires: -1 to force the client to always pull a new copy from the server. This value is new in ASP.NET 1.1.

ServerAndPrivate

Sets the HTTP Cache-Control header to Private but still allows the response to be cached on the server. This value is new in ASP.NET.

Unfortunately, the page output Cache API gets more complicated when we want to accomplish vary by behavior. First we’ll examine how the page output Cache API allows us to use vary by syntax, and then we’ll examine some of the other unique capabilities of the page output Cache API.

Vary By Options with the Output Cache APIs

The behavior of varying by parameters is the same for both the OutputCache directive and the output Cache APIs; however, the syntax is different.

VaryByParams is a property of HttpCachePolicy. Programmatically this is accessed as follows:

Response.Cache.VaryByParams["[string]"] = [true/false];   

Using the example we examined earlier of the http://www.asp.net site with the tabindex and tabid query string or POST parameters, the page output Cache APIs would look as follows:

Response.Cache.VaryByParams ["tabindex"] = true; 
Response.Cache.VaryByParams["tabid"] = true;

To vary by headers, we use the VaryByHeaders property, which has a similar syntax to VaryByParams:

Response.Cache.VaryByHeaders["Accept-Language"] = true;

This syntax is very unlike the OutputCache directive that allows us to specify these items in a semicolon-separated list. However, the end result is the same.

Tip

Setting the Boolean value to true for VaryByParams or VaryByHeaders indicates that the output cache is to be varied by the parameter or header. Programmatically, you can decide not to vary by that particular parameter or header later in the processing of the page execution, and false could be set to indicate this behavior.

Varying by Browser

Varying the output cached page by browser type by passing in the browser string can still be accomplished using the output Cache APIs. However, unlike the other vary by options, this method is virtual:

Response.Cache.SetVaryByCustom("browser");  

This method can be overridden, as you learned earlier in this chapter.

Honoring or Ignoring Cache Invalidation Headers

One last method shown in all the code samples that deserves some attention is the SetValidUntilExpires method. The default behavior of the page OutputCache directive and the output Cache APIs is not identical due solely to the existence of the SetValidUntilExpires method. The SetValidUntilExpires method controls a nuance of how the ASP.NET page output cache honors the HTTP cache invalidation headers.

Cache invalidation headers are sent by browsers, such as Internet Explorer, Netscape, and Opera. Browsers send HTTP headers for certain browser actions such as when the Refresh button is clicked. When the Refresh button is clicked, the browser sends instructions with its HTTP request, effectively stating that any cached versions of the document being requested are not to be served from any cache.

Sample Browser/Web Server Session

Let’s take a look at a sample browser/Web server HTTP session with http://www.asp.net (only the client headers are shown). Here is the initial request:

GET http://www.asp.net/ HTTP/1.0
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705)
Host: www.asp.net
Proxy-Connection: Keep-Alive

This is the code generated when the Refresh button is clicked in the browser:

GET http://www.asp.net/ HTTP/1.0
Accept-Language: en-us
Pragma: no-cache
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705)
Host: www.asp.net
Proxy-Connection: Keep-Alive

The important HTTP header relevant for caching sent by the client is Pragma: no-cache. This HTTP header indicates that the origin server is to be contacted and the page requested anew. The goal of this HTTP header is to ensure that in complex caching scenarios, where caching hardware potentially has invalid copies of the requested page, the client can override any cached versions of the page and re-request the document from the server that originally created the page.

Note

The Pragma: no-cache HTTP header is not officially an HTTP version 1 behavior and is replaced in HTTP 1.1 with the Cache-Control header. However, like many characteristics of HTTP, the standard is only loosely followed. Nearly all browsers still use Pragma: no- cache and thus ASP.NET must know how to process it.

When SetValidUntilExpires is false, the Pragma: no-cache browser headers invalidate the output cached page from ASP.NET’s cache because ASP.NET honors HTTP cache invalidation headers. When SetValidUntilExpires is true, ASP.NET ignores the request to invalidate the page output cache, keeping the output cached page in memory.

Tip

When using the page output Cache APIs, always set SetValidUntilExpires to true unless you want clients to be able to remove your output cached pages from memory. The output cache is a performance enhancement, and if clients can arbitrarily remove pages from the cache, performance suffers.

When you use the page output Cache APIs and SetValidUntilExpires is not specified, the method defaults to false. Conversely, when using the page OutputCache directive, SetValidUntilExpires defaults to true! (In fact, you cannot control the behavior of SetValidUntilExpires using the page OutputCache directive.)

Although the default between the two is inconsistent, the belief is that it is more common to want the page to remain in cache when using the page OutputCache directive. However, when using the page output Cache APIs, more consistency with HTTP cache semantics is the default.

Note

Why have differing default behaviors for the page OutputCache directive and the page output Cache API? The HTTP specification mandates that documents with HTTP GET (querystring) or POST parameters not be cached. However, we felt that the developers writing the application were more qualified to decide whether the page should be cached. Most developers desire the document to remain in the cache when that behavior is specified, rather than allow a client browser to evict the page from the output cache by simply refreshing the page in the browser.

Thus, to mirror the behavior of the OutputCache directive that caches a document for 60 seconds, we need to—at a minimum—specify the following:

Response.Cache.SetExpires(DateTime.Now.AddMinutes(60)); 
Response.Cache.SetValidUntilExpires(true);

Note

Several additional methods are supported by the page output Cache API but are not covered in this book. Many of these have more to do with the nuances of HTTP cache behaviors than caching content, and thus are rarely used by most developers. For more information about these APIs, the product documentation provides excellent coverage.

Deterministically Serving Pages from the Cache

Now that we’ve discussed achieving parity behavior with the OutputCache directive, let’s look at some of the APIs offered only by Response.Cache:

  • Validation callback

  • Dependencies

  • Programmatically removing output cached pages

  • SetAllowResponseInBrowserHistory method

We can deterministically serve pages from the cache by utilizing a less- known feature of the page output cache API: validation callbacks. A validation callback allows us to wire in some code that is called before an output cached page can be served from the output cache. This wire-up is done using the Response.Cache.AddValidationCallback method and allows us to specify a delegate method that will be called through.

The delegate method must follow the method prototype defined by the HttpCacheValidateHandler constructor:

public void Validate(HttpContext context, 
Object data,
out HttpValidationStatus status) {
}

We can then wire up the delegate by specifying the Validate method as the parameter of the Response.Cache.AddValidationCallback method:

public void Page_Load(Object sender, EventArgs e) {
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(true);
Response.Cache.AddValidationCallback(Validate);
}

Now whenever the page is served from cache, the Validate method is called through. Within the method, we can perform the necessary logic to determine whether the requested page is still valid. We simply need to set the status out parameter to one of three possible values of the HttpValidationStatus enumeration:

  • IgnoreThisRequest Leave the output cached page in the cache and execute the page.

  • Invalid Remove the output cached page from the cache and execute the page.

  • Valid Serve the request from the output cache.

For example, if we were running a reporting service that provided time- critical information, we might decide that we never serve cached content to paying customers, but to anonymous customers, we always serve from the cache when it’s available. Here’s the validation callback method:

public void Validate(HttpContext context, 
Object data,
out HttpValidationStatus status) {

// Is the request from an anonymous user?
//
if (!context.Request.IsAuthenticated) {
status = HttpValidationStatus.Valid;
return;
}

// Request must be from a paying customer
//
status = HttpValidationStatus.IgnoreThisRequest;

}

This code checks the Request.IsAuthenticated property to determine whether an authenticated user is making a request. (An authenticated user is one who is signed in; we would likely want to base this authenticated user status on a user role in your actual program.) If the request is not authenticated, the request can be served from the cache. If the request is authenticated, we guarantee that the page requested is executed while leaving the output cached version in memory.

Removing Pages with Dependencies from the Cache

As it applies to page output caching, dependencies allow us to remove output cached pages when external but related dependent items change. For example, the output cached page is automatically made dependent upon the file or files used to create it, including the page (.aspx file) as well as any associated user controls or include files. If any of these files change, the output cached page is automatically removed from the cache. (We’ll look more at dependencies later in this chapter when we examine the Cache API.)

In many cases, it is desirable to make pages dependent upon other files or other types of resources. You can do this by using APIs found on the Response object:

AddFileDependencies(ArrayList filenames) 
AddFileDependency(string filename)
AddCacheItemDependencies(ArrayList cacheKeys)
AddCacheItemDependency(string cacheKey)

Note

In version 2 of ASP.NET, we’ll add a AddCacheDependency method to allow you to add an instance of CacheDependency directly.

The first set of APIs allow the output cached page to be made dependent upon any file. For example, if the page relies on several XML files used as persistent data stores, the output cached page could be made dependent upon these files:

public void Page_Load(Object sender, EventArgs e) {

// Make dependent upon files
ArrayList files = new ArrayList();
files.Add(HttpServer.MapPath("Products.xml"));
files.Add(HttpServer.MapPath("Customers.xml"));
files.Add(HttpServer.MapPath("Sales.xml"));

// Setup to output cache this page
Response.AddFileDependencies(files);
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(true);
}

If any of these files change, the page is evicted from the output cache.

Note

No race condition exists when creating a dependency. If the dependent item changes before the item is inserted into the cache, the insert fails and the item is not added to the cache.

Tip

If the page you are output caching relies upon file resources other than those used to execute the page, use the output Cache APIs and make the page dependent upon those files. If the files change, the output cached page will be evicted from the output cache.

Another, more powerful, technique is to make the output cached page dependent upon other cache entries. If these other cache entries change, the output cached page is evicted from the output cache. This approach is more powerful because a relationship can be made between multiple cached items. For example, an item can be added programmatically to the cache and a page can then be made dependent upon it. This code snippet stores some product details:

public void Page_Load(Object sender, EventArgs e) {

if (Cache["ProductDetails"] == null) {

// Store product details in the Cache API
DataSet ds = GetProductDetails();

Cache["ProductDetails"] = ds;
}
}

The second bit of code depends on the first:

 public void Page_Load(Object sender, EventArgs e) {

// Setup to output cache this page
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.VaryByParam["ProductId"] = true;
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(true);

// Make dependent upon ProductDetails cache entry
// if it exists
//
if (Cache["ProductDetails"] != null)
Response.AddCacheItemDependency("ProductDetails");

}

The first snippet stores a DataSet in the Cache using the key ProductDetails. The second snippet is then made dependent upon the "ProductDetails" cache entry if it exists.

Cache key dependencies allow you to build cascading removal of entries in the cache, as shown in Figure 6-7. In the figure, there are three cached DataSets and three cached pages. If the SalesReport cache entry were changed or removed, all dependent entries would be removed. However, if the Products cache entry were changed or removed, only the cache entries dependent upon Products would also be removed.

click to expand
Figure 6-7: A cache key dependency relationship

Tip

Use cache key dependencies where caching is used to enforce behaviors throughout your application. Dependencies do have additional overhead, but for scenarios such as those described thus far, dependencies allow for some powerful behaviors. For example, every page could theoretically be dependent upon a common cache key. When an administrator wanted to flush the cache, she would simply need to invalidate the common key. This would then evict all output cached pages from memory.

Programmatically Removing Output Cached Pages

It is possible to force the eviction of any output cached page using the static HttpResponse.RemoveOutputCacheItem method. This little-known method allows for easy removal of any output cached page. For example, if we wanted to evict the output cached page /Products/ProductDetail.aspx, we could simply call the following:

HttpResponse.RemoveOutputCacheItem("/Products/ProductDetail.aspx");  

This would evict the named page (and all of its associated pages) from the ASP.NET output cache.

Tip

The technique we just described works well in a single server environment. In a Web farm environment, however, the static method HttpResponse.RemoveOutputCacheItem would need to be called on each server since the cache is not shared.

Controlling Whether the Page Can Live in the Browser’s History

Here is a new method added in ASP.NET 1.1:

Response.Cache.SetAllowResponseInBrowserHistory 

This API allows the page developer to control whether the output cached page can be stored in the browser’s history, for example, when you navigate using the browser’s Back and Forward buttons.

The default value is true, meaning that the browser can store the output cached page in browser history, honoring the value set in the HTTP Expires header. However, if the value is set to false, ASP.NET will set the HTTP Expires header to -1. The Expires: -1 setting simply tells the browser that any request for the page must be directed back to the server because the page does not have a future expiration value.

SetAllowResponseInBrowserHistory is a method that allows you to ensure that the browser can never cache a version of the page in the browser’s history. This is useful for scenarios in which data, such as stock quote information, can change significantly.




Microsoft ASP. NET Coding Strategies with the Microsoft ASP. NET Team
Microsoft ASP.NET Coding Strategies with the Microsoft ASP.NET Team (Pro-Developer)
ISBN: 073561900X
EAN: 2147483647
Year: 2005
Pages: 144

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net