Optimizing for website performance includes setting long expiration dates on our static resources, such s images, stylesheets and JavaScript files. Doing that tells the browser to cache our files so it doesn’t have to request them every time the user loads a page. This is one of the most important things to do when optimizing websites.

In ASP.NET on IIS7+ it’s really easy. Just add this chunk of XML to the web.config’s <system.webServer> element:

<staticcontent>
  <clientcache cachecontrolmode="UseMaxAge" cachecontrolmaxage="365.00:00:00" />
</staticcontent>

The above code tells the browsers to automatically cache all static resources for 365 days. That’s good and you should do this right now.

The issue becomes clear the first time you make a change to any static file. How is the browser going to know that you made a change, so it can download the latest version of the file? The answer is that it can’t. It will keep serving the same cached version of the file for the next 365 days regardless of any changes you are making to the files.

Fingerprinting

The good news is that it is fairly trivial to make a change to our code, that changes the URL pointing to the static files and thereby tricking the browser into believing it’s a brand new resource that needs to be downloaded.

Here’s a little class that I use on several websites, that adds a fingerprint, or timestamp, to the URL of the static file.

using System; 
using System.IO; 
using System.Web; 
using System.Web.Caching; 
using System.Web.Hosting;

public class Fingerprint 
{ 
  public static string Tag(string rootRelativePath) 
  { 
    if (HttpRuntime.Cache[rootRelativePath] == null) 
    { 
      string absolute = HostingEnvironment.MapPath("~" + rootRelativePath);

      DateTime date = File.GetLastWriteTime(absolute); 
      int index = rootRelativePath.LastIndexOf('/');

      string result = rootRelativePath.Insert(index, "/v-" + date.Ticks); 
      HttpRuntime.Cache.Insert(rootRelativePath, result, new CacheDependency(absolute)); 
    }

      return HttpRuntime.Cache[rootRelativePath] as string; 
  } 
}

All you need to change in order to use this class, is to modify the references to the static files.

Modify references

Here’s what it looks like in Razor for the stylesheet reference:

<link rel="stylesheet" href="@Fingerprint.Tag("/content/site.css")" />

…and in WebForms:

<link rel="stylesheet" href="<%=Fingerprint.Tag(" />content/site.css") %>" />

The result of using the FingerPrint.Tag method will in this case be:

<link rel="stylesheet" href="/content/v-634933238684083941/site.css" />

Since the URL now has a reference to a non-existing folder (v-634933238684083941), we need to make the web server pretend it exist. We do that with URL rewriting.

URL rewrite

By adding this snippet of XML to the web.config’s <system.webServer> section, we instruct IIS 7+ to intercept all URLs with a folder name containing “v=[numbers]” and rewrite the URL to the original file path.

<rewrite>
  <rules>
    <rule name="fingerprint">
      <match url="([\S]+)(/v-[0-9]+/)([\S]+)" />
      <action type="Rewrite" url="{R:1}/{R:3}" />
    </rule>
  </rules>
</rewrite>

You can use this technique for all your JavaScript and image files as well.

The beauty is, that every time you change one of the referenced static files, the fingerprint will change as well. This creates a brand new URL every time so the browsers will download the updated files.

FYI, you need to run the AppPool in Integrated Pipeline mode for the <system.webServer> section to have any effect.

Update Jan 1st 2014. TypeScript support is back in Web Essentials!

When Web Essentials was ported to Visual Studio 2013, a lot of features were removed. This was because many of those features were built directly in to Visual Studio 2013 and therefore no longer needed support by Web Essentials. That’s not exactly the reason why the support for TypeScript was removed.

TypeScript has been evolving at a steady pace since it was released in the fall of 2012. It’s now up to version 0.9 and that means that the final 1.0 release is getting closer. The Compile on save feature introduced by Web Essentials 2012 has now been rolled into TypeScript and the feature will be broadened in the months to come. That means Compile on save will be natively available for more project types, including ASP.NET projects, in the foreseeable future. If both Web Essentials and the TypeScript tooling provide the exact same functionality, there is bound to be conflicts.

You might remember what happened when LESS and CoffeeScript tooling was moved from Web Essentials into Visual Studio 2012 Update 2. Unless you updated both at the same time, Visual Studio would crash. The Visual Studio Web Team had to write a blog post about it and even ScottGu mentioned it. It was bad. Really bad.

In order not to repeat the same mistake twice, we’ve opted for not having TypeScript tooling in Web Essentials. That’s not to say we’ll never add extra TypeScript features in Web Essentials, but it means that we are doing what we can to avoid any collisions between the two toolsets.

As a TypeScript user you should take this as a positive thing, because it means that TypeScript is getting closer to its final 1.0 release and it will have some of the features you used to rely on Web Essentials to provide.