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 }