Problem. Caching with HTTP headers is important to every web site, but there are many complicated rules and conflicts among the headers. You want to simplify and improve your HTTP cache headers. Solution. Here are lots of notes about HTTP headers and their methods in ASP.NET.
ASP.NET websites may use ASPX files or ASHX handlers to serve static content like images. Keeping with Yahoo's guidelines, use this ASP.NET C# code to set Expires in the far future.
using System;
using System.Web.UI;
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Set this ASPX response to expire in one year
// on browsers, essentially 'never'.
Response.Cache.SetExpires(DateTime.Now.AddYears(1));
}
}IIS7 will set the Cache-Control header automatically when you specify SetExpires. No other caching logic is necessary for static resource caching on client browsers.
Almost all big websites have certain images that never change, such as logos, shopping cart images, rounded borders and gradients, and navigation bars.
Yahoo says to use "the Expires header in the HTTP response to tell the client how long a component can be cached." [Best Practices for Speeding Up Your Web Site - developer.yahoo.com]
Yahoo introduces this example: "This is a far future Expires header, telling the browser that this response won't be stale until April 15, 2010."
Expires: Thu, 15 Apr 2010 20:00:00 GMT
Note that the time format is very specific and you will need to use a special DateTime format in C# to create or test it. [See section on DateTime]
Yahoo recommends the Cache-Control header for dynamic pages. There are several variants of this you can use.
Yahoo's best practices: "For dynamic components: use an appropriate Cache-Control header to help the browser with conditional requests."
Using Cache-Control gives you overriding power on the cache setting, allowing you to specify options for proxies and the server.
The implementation of caching on Response.Cache is very complex and confusing in ASP.NET, and some options will trigger other options. Setting a page for 1 hour of caching is done like this:
using System;
using System.Web;
using System.Web.UI;
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Set cache for 1 hour on all computers and servers.
// Proxies, browsers, and your server will cache it.
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0));
}
}As Yahoo states, Cache-Control gives you more control and "helps browsers" with conditional requests.
When developing ASP.NET applications with caching, you should use Fiddler, or another equivalent tool, to look at the HTTP headers. I have written material on Fiddler. [Fiddler Tool for HTTP Debugging - dotnetperls.com]
These methods and properties are used to control the HTTP cache settings on your ASP.NET response. They must be called on the HttpResponse object, using the Response.Cache...() style syntax.
| Method name | Its usage |
| AddValidationCallback | You will need this when using callbacks, which I have no experience with. |
| AppendCacheExtension | You can use this to add a custom header to the Cache-Control header, which could be used for future changes in HTTP 1.1 or proprietary options. |
| SetAllowResponseInBrowserHistory | This overrides certain settings made by SetCacheability, such as NoCache and ServerAndNoCache. [SetAllowResponseInBrowserHistory - msdn.microsoft.com] |
| SetCacheability | This is important and sets the Cache-Control header, which is the preferred mechanism for caching dynamic pages. See the list of HttpCacheability enums below. |
| SetETag | This allows you to specify a string that is considered the 'tag' of a resource. This is not recommended by Yahoo and not normally needed. |
| SetETagFromFileDependencies | Tells ASP.NET to assign random etags to your resources that are keyed to the file contents. Simplifies ETag usage. Not normally needed. |
| SetExpires | Very important and useful for static resources such as logo images or web site layout images. Recommended by Yahoo for static resources. |
| SetLastModified | This can be used to date your file and return a 304 when a user requests the same one again. This doesn't save an HTTP request. Yahoo recommends modified dates over ETags. |
| SetLastModifiedFromFileDependencies | Same as above but tells ASP.NET to read in the file metadata automatically. |
| SetMaxAge | Very important. This gives you a relative time window you can specify a resource can be cached for. This is an alternative to the Expires header, and it overrides the Expires header. |
| SetNoServerCaching | This seems to remove the HttpCacheability.Server setting. It seems like a really poor design in ASP.NET. |
| SetNoStore | Applies the "Cache-Control: no-store" header. This is useful for advertisements and dynamic responses. |
| SetNoTransforms | Some proxy caches can change the format of your files when they store them. This setting should tell them not to. |
| SetOmitVaryStar | Changes header when using vary parameters. Not often useful. |
| SetProxyMaxAge | Not likely to be useful. It suggests that proxy caches can expire or keep resources for a specific time. I doubt they would honor this exactly. |
| SetRevalidation | Indicates when validation should occur. See Cache-Control header section. |
| SetSlidingExpiration | Changes the logic of when the server expires its cache. Has many quirks and you must test it carefully. [ASP.NET - Expiration Time and Cache - dotnetperls.com] |
| SetValidUntilExpires | Don't listen to browsers when they say a resource is expired or stale. Otherwise, they can invalidate caches. |
| SetVaryByCustom | Allows you to set the custom vary header, which is useful when you have the Vary header. See section on Vary. |
| VaryByContentEncodings VaryByHeaders VaryByParams | These are public getters only, meaning you cannot set these properties. They are useful for debugging and diagnostics of your Vary header. |
You need to call SetCacheability on the Response.Cache to set the main Cache-Control header. This header controls the location and general behavior of the cache.
You need to combine this setting with other Cache class method calls to achieve many behaviors. However, these enums define the general setting.
| Enum value | Its usage |
| HttpCacheability.NoCache | Tells the browser, the server, and proxies to never cache this resource. This can be useful for advertisements and resources that are always changing. |
| HttpCacheability.Private | Only cache on the browser. This will provide bandwidth savings for your users, but your server won't store a cached copy of the output. This is adequate for many sites. |
| HttpCacheability.Public | The ultimate cache setting: tells the server to save the page, proxy caches to save the page, and the browser to save the page. |
| HttpCacheability.Server | Only cache the page on the server (output caching without browser caching). However, when your visitors click on your static pages, they will be reloaded. |
| HttpCacheability.ServerAndNoCache | The same as NoCache except it allows the server to store the page. Has slightly different meaning for remote clients and proxies. Not often useful. |
| HttpCacheability.ServerAndPrivate | Tells proxy caches to never cache this page, but to allow the browser and the server to cache it. |
Private is a term that means the web browser on your users' computers. By using private, your users won't download or re-request the pages they have viewed.
This reduces the work your server does and the bandwidth you use, but it isn't optimal for optimization. It refuses proxy servers and your own server caches, meaning you still have much CPU use and bandwidth use.
Max-Age, part of the Cache-Control, is relative to the current time, making it easier to use in many cases. It "specifies the maximum amount of time that an representation will be considered fresh."
"Similar to Expires, this directive is relative to the time of the request, rather than absolute." [Caching Tutorial - mnot.net]
From the HTTP spec: "If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header." [HTTP 1.1 Specification]
I am not certain how common proxy caches are, but when available, they can reduce your bandwidth and improve access times. Basically, they can serve up your pages for you, for free.
The Squid software "reduces bandwidth and improves response times by caching and reusing frequently-requested web pages. [It] optimises the data flow between client and server." [squid: Optimising Web Delivery - squid-cache.org]
If you look at the HTTP headers of MySpace, for example, you see the header "Vary: Accept-Encoding". This means that the caches will be separate for each encoding. This improves localization. You can use SetVaryByCustom to change this.
Handler.ashx is the default name for an ASP.NET generic handler. You need to use the HttpContext parameter and access the Response that way.
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
public class Handler : IHttpHandler {
public void ProcessRequest (HttpContext context) {
// Cache this handler response for 1 hour.
HttpCachePolicy c = context.Response.Cache;
c.SetCacheability(HttpCacheability.Public);
c.SetMaxAge(new TimeSpan(1, 0, 0));
}
public bool IsReusable {
get {
return false;
}
}
}When you use the Expires header on static resources such as JavaScript, CSS, and images, you do not need the Cache-Control header. Cache-Control and Expires are alternatives and are not both needed.
You can disable the HTTP Cache-Control header entirely in Web.config. Note however that this setting is defeated if you change cache headers during execution. [httpRuntime Element - MSDN]
<?xml version="1.0"?>
<configuration>
<appSettings/>
<connectionStrings/>
<system.web>
<httpRuntime sendCacheControlHeader="false"/>
<!-- etc. -->You can use a special format string, after converting the date into the current timezone. HTTP has a rigid definition of date formats, which is good.
using System;
using System.Web.UI;
public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
DateTime d = DateTime.UtcNow;
string s = d.ToUniversalTime().ToString("r");
Response.Write(s);
}
}For more information on HTTP date formats, such as the RFC1123 pattern, see the Microsoft site. [Standard Date and Time Format Strings - msdn.microsoft.com]
There is a Cache object you can use to store data programmatically. This is separate from the methods shown here, and has many different options. This document focuses on HTTP headers.
The examples above show how you can access it in the Page class and also from generic handlers. However, Cache is an intrinsic object, and my research shows what happens when you access it. [ASP.NET - HttpContext and Request Property - dotnetperls.com]
My research indicates that accessing the Response.Cache property in each method call may not be ideal, and storing the HttpCachePolicy as a variable is better. [ASP.NET - Response.Write Improvement - dotnetperls.com]