This might seem obvious to some folks, but to others it's not, so it's worth mentioning. Regardless, it's a good example of a "white box" attitude. Don't assume. Always assert your assumptions with good tests.
A client wanted to know how to 'force' a client to update some javascript that the browser had cached. The easy answer is "change the file."
Here's what happens with a single HTML file and a single JavaScript file, running locally on my machine. The main directory is set to "Expire Immediately" via IIS's properties dialog. That means "keep it fresh."
Underneath the main directory is a directory called /js that is set to expire in 7 days, as seen at right.
Here's an abridged HTTP Header view (via ieHttpHeaders) after hitting the page for the first time ever important stuff in bold.
GET /javascriptcachingtest/default.htm HTTP/1.1User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1)
HTTP/1.1 200 OKServer: Microsoft-IIS/5.1X-Powered-By: ASP.NETCache-Control: no-cacheExpires: Fri, 12 May 2006 19:03:59 GMTDate: Fri, 12 May 2006 19:03:59 GMTContent-Type: text/htmlLast-Modified: Fri, 12 May 2006 18:53:33 GMTETag: "b01be5ef575c61:df3"Content-Length: 115
GET /javascriptcachingtest/js/test.js HTTP/1.1Referer: http://localhost/javascriptcachingtest/default.htmUser-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1)Connection: Keep-Alive
HTTP/1.1 200 OKServer: Microsoft-IIS/5.1X-Powered-By: ASP.NETCache-Control: max-age=604800Expires: Fri, 19 May 2006 19:03:59 GMTDate: Fri, 12 May 2006 19:03:59 GMTContent-Type: application/x-javascriptLast-Modified: Fri, 12 May 2006 18:54:28 GMTETag: "50b1c1d4f775c61:df3"Content-Length: 151
Note that both files were returned with HTTP 200 OK and the Javascript file had a Last-Modified header returned and an Expires date a week in the future. Now I'll hit F5 to refresh.
HTTP/1.1 200 OKServer: Microsoft-IIS/5.1X-Powered-By: ASP.NETCache-Control: no-cacheExpires: Fri, 12 May 2006 19:11:30 GMTDate: Fri, 12 May 2006 19:11:30 GMTContent-Type: text/htmlLast-Modified: Fri, 12 May 2006 18:53:33 GMTETag: "b01be5ef575c61:df3"Content-Length: 115
GET /javascriptcachingtest/js/test.js HTTP/1.1Referer: http://localhost/javascriptcachingtest/default.htmIf-Modified-Since: Fri, 12 May 2006 19:03:59 GMTIf-None-Match: W/"50b1c1d4f775c61:df3"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1)Host: localhostConnection: Keep-Alive
HTTP/1.1 304 Not ModifiedServer: Microsoft-IIS/5.1Date: Fri, 19 May 2006 19:03:59 GMTX-Powered-By: ASP.NETCache-Control: max-age=604800Expires: Fri, 19 May 2006 19:03:59 GMTETag: "50b1c1d4f775c61:df3"Content-Length: 0
Note that the JavaScript file wasn't return (Content-Length: 0), the ETag is the same, and instead a 304 Not Modified was returned. This is the essense of client side caching and is something you should be exploiting (Sadly, fewer folks than you think do this) to get good throughput, efficiency and save on bandwidth costs.
Now, I'll "touch" the file - change it's modified date using the touch.exe I've got in my c:\utils folder (from http://unxutils.sourceforge.net/). Of course, there are other ways to do this, but you get the idea.
We've touched the file, so we'll hit F5 again to refresh:
GET /javascriptcachingtest/js/test.js HTTP/1.1Accept: */*Referer: http://localhost/javascriptcachingtest/default.htmAccept-Language: en-usAccept-Encoding: gzip, deflateIf-Modified-Since: Fri, 12 May 2006 19:03:59 GMTIf-None-Match: "50b1c1d4f775c61:df3"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1)Host: localhostConnection: Keep-Alive
HTTP/1.1 200 OKServer: Microsoft-IIS/5.1X-Powered-By: ASP.NETCache-Control: max-age=604800Expires: Fri, 19 May 2006 19:11:30 GMTDate: Fri, 12 May 2006 19:11:30 GMTContent-Type: application/x-javascriptLast-Modified: Fri, 12 May 2006 19:11:29 GMTETag: W/"804647dff775c61:df3"Content-Length: 151
Notice that the browser asks for the JavaScript not only "by name" but also by date, and by ETag, a mostly unique identifier. The IIS server responds with an HTTP 200 OK, returning the freshly changed (in IIS's mind) file along with a new ETag and a new Last-Modified date.
As an aside, DasBlog does a pretty good job in its RSS Syndication Code of programmatically managing If-Modified-Since behavior. Remember that ASP.NET's <%OutputCache%> is SERVER-SIDE. It's not what we've just seen here. If you want this kind of behavior in your ASP.NET code, you'll need to do it manually in code. I'll post examples of that later.
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.