Ant
1 note

IfModifiedAttribute

Caching can dramatically improve the performance of your ASP.NET MVC project and adding support for ETag can give even more still.

When you are building an app with plenty of generated content its a no-brainer to add Caching, this can be done using the OutputCacheAttribute, for example

[OutputCache(
    Duration = 3600,
    Location = OutputCacheLocation.ServerAndClient, 
    VaryByParam="id; thumb")]
public ActionResult PersonPhoto(int id, bool thumb)

This will store the output from this method on the server for 3600 seconds, it will also tell the browser to store the response in the browsers cache for the same amount of time. The problem here is when I update my Photo, none of the client browsers will ask for an update until the 3600 time is up. You can clear the cache on the server, which means new client requests will get the new photo, but clients which have cached the response will still be out of date.

What you need to do is;

  1. Tell the client to cache the photo and an ETag for unique version identification
  2. The client will then make a request, returning the ETag to the server
  3. The server will check the date against the cache
  4. If no change has been made it need not return the data again - just a "304 - Not Modified" response.
  5. If there is a change send the new response with a new ETag

IfModifiedAttribute

This is an attribute which you can use in the same way as OutputCache, only it will server cache your output on the first call and check subsequent requests for the "If-None-Match" header (which will contain the client cached ETag), responding appropriately.

[IfModified("PersonPhoto", "id")]
public ActionResult PersonPhoto(int id, bool thumb)

The arguments form a key under which all the calls will be cached, in this case a dependency will be created for each unique combination of "PersonPhoto" and the value of "Id". (for example Person_223, where 223 is an id)

The cached result can then be released by calling the Cache.Modified extension when you update dependent data, for example in the post action method...

[HttpPost]
public ActionResult PersonPhoto(
        int id,
        HttpPostedFileWrapper photo) {
    if (ModelState.IsValid) {
        var client = new PersonServiceClient();

        client.SavePhoto(id, photo.Resize(200, 200));
        HttpContext.Cache.Modified("PersonPhoto", id);
    }

    return View();
}

This means that you don't need to choose a duration for the cache, as the clients will always check with the server on each call and only when there is a change will they receive a new response.

Fiddling

The following output from Fiddler where both the page and image methods decorated with the IfModifiedAttribute.

looking at the figures for the first call to the image

Bytes Sent: 858 (headers:858; body:0)
Bytes Received: 80,864 (headers:392; body:80472)

and for the subsequent call

Bytes Sent: 858 (headers:858; body:0)
Bytes Received: 398 (headers:398; body:0)

I'm not showing the timings here as its all hosted locally, but you can see that there is a saving on the second call of ~80KB, this is the size of the image, so you will get different results based on what you are serving, but the benefit is clear

How does it work then?

Well, in a nut-shell, it is an action filter which

  1. Checks the incoming request URL for a cached version.
  2. If none exists then it captures the Response.OutputStream using a Response Filter and caches it using standard caching under the dependency key passed in the attribute .. and then continues as normal.
  3. If one exists it checks whether or not the request contains an "If-None-Match" header and either sends the cached version back or a "304 - Not Modified".
  4. The developer can call the Cache.Modified extension method to clear the cache, this will clear all cached url responses for the matching depenency keys

Simple as that, well there are some complexities to get the browser to return the right headers, you can read all about that in Enabling Client-Side Caching of Generated Content in ASP.NET by Yorai Aminov - thanks Yorai.

Improvements

The initial call is doubling up on memory as the data is stored in the OutputStream and the Caching stream, there maybe a better way to do this.

Using the standard ASP.NET caching means that all cached data will be cleared when the application recycles, because this a coded release mechanism there is no reason why the cached data could live beyond the applications worker process lifetime.

If the data were to be updated by another application the cache would be out of date.

Summing Up

Adding support for the "ETag"/"If-None-Match" headers and "304 - Not Modified" response means that you can reduce the amount of data the server has to supply to a client on subsequent calls for the same URL, this means that the server can deal with more requests in the same time and your application will be faster.

Attachments

IfModifiedAttribute.txt - C# 4.0 class file

IfModifiedAttribute.zip - C# 4.0 class file in a zip

References

Enabling Client-Side Caching of Generated Content in ASP.NET

http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute.aspx

http://msdn.microsoft.com/en-us/library/system.web.httpresponse.filter.aspx

http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304

http://en.wikipedia.org/wiki/HTTP_ETag

Ant

Updated to use ETag only rather than last modified, thanks to Mike Hadlow

As IE only works when an ETag is supplied, the last modified date is redundant. I have tested this in the latest browsers, IE9 (+compat mode), Chrome 9, FireFox 3.6 and Safari 5


Post a Note

(required)

(required never shown)

On Twitter Follow MrAntix on Twitter

15/05/2012
WindowsAzure
Announcing the MEET Windows Azure Event! Streamed online June 7th. Register at http://t.co/bObzTAuL  #MEETAzure #WindowsAzure

10/05/2012
kevinwhinnery
Comparing Titanium and PhoneGap - A common question I get asked at developer events and conferences is how... http://t.co/Zq2eND6B

09/05/2012
brianleroux
PhoneGap goals and philosophy: http://t.co/wkq8wI2T

just now
mstechfeed
#MSDN Virtual Lab: #Windows Azure: Running a Parametric Sweep Applications with the Windows Azure HPC Scheduler http://t.co/3676GXSQ

just now
ilcomm
#android #jobs encrypting phonegap android by joseesfera http://t.co/s4VgtPhs #israel #iphone