Forcing The Browser To Cache Dynamic Content In ASP.NET

Usually, you don’t want the browser doing any kind of caching on dynamic content served from a generic handler (.ASHX) in ASP.NET – afterall, the content is usually changing (dynamic). Sometimes however, it’s handy to use a handler to serve content that effectively never changes. Here’s how.

One common example (that we make use of heavily in EasyAs123Web.com) is using a handler to serve and generate images on-the-fly. Our user images are stored in the database, and are immutable – they never change. Hence we’d like the client browser to cache the image returned.

Example Handler

Let’s start with the shell of a generic handler, MyHandler.ashx.cs:

public class MyHandler : IHttpHandler
{
  public void ProcessRequest( HttpContext context )
  {
    ...
  }

  public bool IsReusable
  {
    get
    {
      return true;
    }
  }
}

Further code will go where I’ve marked with ellipses.

Reponse Headers

For some reason, ASP.NET automatically adds headers that stop the browser from caching the content, and you can’t just override them. Instead, you’ll need to clear them all out first:

context.Response.ClearHeaders();

Next we want to mark the content as fully cacheable, and expiring at some date in the future:

context.Response.Cache.SetValidUntilExpires( true );
context.Response.Cache.SetCacheability( HttpCacheability.Public );
context.Response.Cache.SetExpires( DateTime.Now.AddMonths( 1 ) );
context.Response.Cache.SetLastModified( DateTime.Now.AddMonths( -1 ) );

(I’ve marked the content as expiring in a month, but you could easily add a year or ten years or whatever).

If you are able to calculate some form of hash for your content (which you should be able to do pretty easily), you can add an ETag:

Guid hash = ...;

context.Response.Cache.SetETag( "\"" + hash.ToString().Replace( "-", "" ) + "\"" );

Finally, there’s one more trick which is incredibly useful for allowing browsers to avoid re-downloading the content.

Browsers may include the “If-Modified-Since” request header. If you ignore this header, no problems occur: you just send the content as usual. However, you can use it to inform the browser that your content hasn’t changed since the last time it was requested.

If your content really hasn’t changed, instead of sending a “200 OK” response with all the content, you can send back a “304 Not Modified” without any content whatsoever:

var textIfModifiedSince = context.Request.Headers["If-Modified-Since"];

if( !string.IsNullOrEmpty( textIfModifiedSince ) )
{
  context.Response.Status = "304 Not Modified";
  context.Response.End();

  return;
}

So instead of sending back a potentially large piece of content, you can send back just the tiny response headers.

You can do a similar trick with “If-None-Match” – I’ll leave that as an exercise.

Debugging

Making these changes is all very well, but can you be sure that your changes are actually having the desired effects?

There are two great tools you can use to actually view the requests sent by the browser, and the response sent from your web-app:

If you care about the speed and bandwidth usage of your ASP.NET application, I’d say that these two are pretty much indispensable. You’ll want to test with both: different browsers make subtly different requests, and interpret the responses differently.

You might also want to use YSlow for Firebug: this has some nice graphical displays of page assets.

Example – Content Not In Cache

Request and response, some lines omitted for clarity.

GET /!!/_Serving/StaticFile/Style/Pro/Images/HomeCreate.jpg HTTP/1.1
Host: www.easyas123web.com
HTTP/1.x 200 OK
Cache-Control: public
Content-Type: image/jpeg
Expires: Mon, 01 Jun 2009 08:58:36 GMT
Last-Modified: Wed, 01 Apr 2009 08:58:36 GMT
Etag: "45403f772717d1c63c0b8774e4a124b7"
Content-Length: 20696
... 20K of content here ...

Example – Content In Cache

Request and response, some lines omitted for clarity.

GET /!!/_Serving/StaticFile/Style/Pro/Images/HomeCreate.jpg HTTP/1.1
Host: www.easyas123web.com
If-Modified-Since: Wed, 01 Apr 2009 08:58:36 GMT
HTTP/1.x 304 Not Modified
Cache-Control: public
Expires: Mon, 01 Jun 2009 09:03:51 GMT
Last-Modified: Wed, 01 Apr 2009 09:03:51 GMT
Etag: "45403f772717d1c63c0b8774e4a124b7"

4 Responses to “Forcing The Browser To Cache Dynamic Content In ASP.NET”

  1. Thank you! This was just what I needed for my website that is resizing images dynamically.

  2. Thank you! That was what I needed!

  3. This is indeed a wonderful insight. Dynamic clustering is the future as it addresses issues like Scalability and Reliability.i have found another article on the same topic. Benefits of using NCache Dynamic Clustering Capabilities.
    I am sure this article can also be very helpful as along with dynamic caching, it also addresses the use of different topologies available for dynamic caching.

  4. We already use this technique and are looking to expand it for all our content along the lines of a CISCO Waas. However we’re finding no matter what dates we set Internet explorer is always pinging the server to check for a 304 even if we told it the content wont expire until 2020.

    Do you have any ideas on how to make the browser stop doing this from the server headers?

Leave a Reply