my6solutions

asp .net, the social web & other distractions

 

Running Apps

Disclaimer

I am in no way affiliated with Microsoft or Google. I am just another developer trying to make a difference. All opinions and observations are usually my own.

Enabling client-side caching on ASP .NET MVC on IIS 6.0

What I thought would just take a few minutes to do turned out to be one big adventure. While working on raya, I was irritated by the slowness of the project. From firebug, it seems that the browser was still asking for and getting all the static files again at every page refresh. I had use StaticFileHandler.cs from the blog entry here. This page also explains in more detail about the code.

The problem that is being addressed here is the situation where an ASP.NET MVC site is running under IIS 6.0 where you don't really have access to the webserver, eg. a hosted environment. In situtations like this, IIS no longer handles the caching or compression of static files. ASP.NET now has to perform this task. In the original StaticFileHandler class, the appropriate cache control Headers are added by 

    1 response.Cache.SetCacheability(HttpCacheability.Public);

    2 response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");

    3 response.Cache.SetMaxAge(DEFAULT_CACHE_DURATION);

    4 response.Cache.SetExpires(DateTime.Now.Add(DEFAULT_CACHE_DURATION));

    5 response.Cache.SetLastModified(lastModified);

However, that didn't seem to work. I had a proper look through this StaticFileHandler class and it seems that it doesn't check for the "If-Modified-Since" header. So it will always return the entire file. Ideally, it should return a Http 304 - Not Modified status to the client telling it to just use the local cached copy.

Using firebug to examine the Request/Response headers and through debugging, it seems that my client was not sending back an "If-Modified-Since" header to the server so the above scenario would not happen at all. After trying out various combinations of parameters and permutations of the above piece of code, still no header. However, under Chrome and IE, I was seeing the header being sent. In the end, it turned out to be a bug with firebug, which is mentioned here. Thank you firebug!

The DeliverFromCache method was modified to implement the check for the "If-Modified-Since" header. The final result is as follows:

    1 private bool DeliverFromCache(HttpContext context,

    2             HttpRequest request, HttpResponse response,

    3             string cacheKey,

    4             string physicalFilePath, ResponseCompressionType compressionType)

    5         {

    6             CachedContent cachedContent = context.Cache[cacheKey] as CachedContent;

    7             if (null != cachedContent)

    8             {

    9                 if (request.Headers["If-Modified-Since"] != null)

   10                 {

   11                     string modSince = request.Headers["If-Modified-Since"];

   12                     if (modSince.IndexOf(";") > 0)

   13                     {

   14                         modSince = modSince.Split(';')[0];

   15                     }

   16                     DateTime modSinced = Convert.ToDateTime(modSince).ToUniversalTime();

   17                     //

   18                     if (DateTime.Compare(modSinced, cachedContent.LastModified.ToUniversalTime()) >= 0)

   19                     {

   20                         context.Response.StatusCode = (int)HttpStatusCode.NotModified;

   21                         return true;

   22                     }

   23                     // unreachable as cache would be invalidated if file was modified but added just in case

   24                     return false;

   25                 }

   26 

   27                 byte[] cachedBytes = cachedContent.ResponseBytes;

   28 

   29                 // We have it cached

   30                 this.ProduceResponseHeader(response, cachedBytes.Length, compressionType,

   31                     physicalFilePath, cachedContent.LastModified, cacheKey);

   32                 this.WriteResponse(response, cachedBytes, compressionType, physicalFilePath);

   33 

   34                 Debug.WriteLine("StaticFileHandler: Cached: " + request.FilePath);

   35                 return true;

   36             }

   37             return false;

   38         }

So instead of sending back the entire file again to the client, the client will just use its local copy when it receives the 304 status.

Another thing that tripped me up was that the date specified in the "If-Modified-Since" does not include miliseconds. Therefore, when you cache the lastModified value, the miliseconds value has to be zeroed. The is achieved by updating the CachedContent class via the following code.

    1 private class CachedContent

    2         {

    3             public byte[] ResponseBytes;

    4             public DateTime LastModified;

    5 

    6             public CachedContent(byte[] bytes, DateTime lastModified)

    7             {

    8                 this.ResponseBytes = bytes;

    9                 // milliseconds in If-Modified-Since header is always 0

   10                 this.LastModified = new DateTime(lastModified.Year,lastModified.Month,

lastModified.Day,lastModified.Hour,lastModified.Minute,lastModified.Second);

   11             }

   12         } 


Bookmark and Share

Permalink | Comments (4) | Post RSSRSS comment feed

Comments

Sameer

Tuesday, June 02, 2009 6:44 AM

Sameer

I think your changes are excellent, Firebug shows that the client (firefox) is not re-downloading the static content, based on the changes you made.

Sameer

Wednesday, June 03, 2009 5:47 AM

Sameer

On second thought I realized I didn't do my test properly.. So you can just ignore that comment above, I need to re-test more carefully.

answerspluto.com

Monday, July 13, 2009 3:29 AM

pingback

Pingback from answerspluto.com

list of links 3 « Answers Pluto

jonny

Friday, July 24, 2009 11:59 PM

jonny

Thanks for good code, buddy! very useful for me