First something about caching

The numerous caching options you have in ASP.NET (MVC) are mainly focused on data and page output caching. But caching also occurs at the webserver, network and browser level.  These you can’t always control from within your code.

When your content leaves your application, it is processed by the webserver, depending on the server and version it has numerous options to control how and when it is cached. When your content is processed by the webserver and sent to the browser, there is also the network that can control the caching, namely proxy and web acceleration servers. Finally the content arrives in the browser and the browser itself has also numerous options related to caching. Generally spoken, they all use the same parameters, or at least some of them, to determine when, what and how long the content should be cached.

How does this caching work? Generally spoken, following rules apply:

  1. If the response header says not to cache, it doesn’t cache
  2. If we use a secure or authenticated transfer, like HTTPS, it doesn’t cache either
  3. If the cache expiring time or any other age-controlling header says it’s still ‘fresh’, it doesn’t cache
  4. If there’s an old version in the cache, the server will be asked to validate the version.  If the version is still good, it is served from the cache.
  5. Sometimes when the server cannot be reached due to network failure or disconnectivity, the content is also directly served from the cache.

Then what parameters are used, and how are they used?

  • HTTP Headers: these are sent in the request, but are not visible in the content
    • Expires: tells the cache how long the content stays fresh. After that time, the cache will always check back with the server. It uses an HTTP date in Greenwich Mean Time (GMT), any other or invalid format will be interpreted as in the pas and makes the content uncachable.  For static data you can set a time in the very far future, for highly dynamic content, you can set a time much closer, or even in the past to have the cache refresh the content more often or at every request.
    • Cache-Control: In response to some of the drawbacks of the Expired header, the Cache-Control header class was introduced. It includes (some, not all):
      • max-age=[seconds]
      • public / private
      • no-cache / no-store
      • must-revalidate
    • Pragma: no-cache: the HTTP specifications aren’t clear of what it means, so don’t rely on it, use the ones above
  • HTML meta tags: Unlike HTTP Headers, HTML meta tags are present in the visible content, more precisely in the <HEAD> section of your HTML page. A huge drawback of the us of HTML meta tags is, is that they can only be interpreted by browsers, and not all of them use them like you would expect. So prefer HTTP headers over HTML meta tags

A great Caching Tutorial can be found here: http://www.mnot.net/cache_docs/, and another one here: Save Some Cash: Optimize Your Browser Cache

An easy solution

Now, all of the caching systems rely in some way on the full request string to identify the content that is being cached.

So, the easiest solution would be to request a new unique URL every time the resource has changed, with a new version number.

How we do it in ASP.NET MVC

ASP.NET MVC (and ASP.NET Webforms also) doesn’t generate a new version number automatically.  You need to tell it to do so in the AssemblyInfo.cs file.  After a default project setup it contains a line like:

[assembly: AssemblyVersion("1.0.0.0")]

The version number is a four-part string with the following format: <major version>.<minor version>.<build number>.<revision>.  You usually set the major and minor version manually, as they are used as the type library version number when the assembly is exported, and don’t (need to) care of the build and revision number.  Well, now we do.

When you change this line to (or add it if it doesn’t exist):

[assembly: AssemblyVersion("1.0.*")]

We tell the compiler to generate a build and revision number for us. The generated build number is the number of days since 1-01-2000 (so 9-08-2010 gives 3873) and the revision number is the number of two second intervals since midnight local time (so a build at 11:59:12 gives 19776).

Now we have instructed our application to generate a new unique build number for us with every build, and every (possible) change of a resource, we can use this number as a unique parameter value in the URL of the the resource.

First we need to pass this version number from controller to view.  In the constructor of the (base)controller we put the version number in the ViewData Dictionary. With the ViewData you easily can pass data from the controller to the view using a key-value pattern.

protected BaseController(){
ViewData["version"] = Assembly.GetExecutingAssembly().GetName().Version;
}

And finally in the view, all you need to do is append this version number to the URL of the files you want to be prevented from caching:

<script type="text/javascript" language="javascript" src="<%: Url.Content("~/Scripts/commonFunctions.js?" + ViewData["version"]) %>"></script>

This makes sure we have a unique URL for our resources and they are not cached by the browser or a proxy.

Of course, like stated above, there are other ways of preventing files from being cached anywhere between the server and the browser, but the advantage of this method is that you don’t need to poke around in IIS settings (in case when you don’t have access to it) and you can define when and which version of the file you want to be cached.  And you can of course use any other method to generate a unique URL.

One more remark: When building a multi-tier application, make sure you set the version number in the AssemblyInfo.cs of the project where you use it, meaning, that if you put your base controller in a shared assembly, you need to specify the version number in the shared assembly project.