Earlier this week we shipped Visual Studio 2013 Update 2 CTP 2. Gotta love that name. The previous CTP 1 release didn’t contain any web specific features, but CTP 2 is packed with web goodness. Here are some of the highlights:

  • Improvements to the LESS editor
    • Nested media queries
    • Named parameters
    • Selector interpolation
    • Go to definition for variables, mixins and @import
    • Many additional tweaks and fixes
  • A brand new SCSS editor
    • With all the bells and whistles you’d expect
  • A brand new JSON editor
    • Basic editor features. More to come in the final release
  • Knockout.js Intellisense updates
    • Nested view model support
    • KO comments
    • Fixes and tweaks
  • New URL picker for HTML, CSS, LESS and SCSS
  • Browser Link updates
    • HTTPS support
    • Static HTML source mapping
    • SPA support for mapping data
    • Auto-update mapping data
  • General updates, tweaks and fixes

Even though the release is “just” a CTP, all these features are very solid and of high quality. I can only recommend giving CTP 2 a try if you haven’t already. There is so much web development awesomeness in it.

Web Essentials doesn’t work with CTP 2

That’s right, Web Essentials 1.9 and earlier doesn’t work with CTP 2 due to API changes in Visual Studio. If you install Web Essentials on CTP 2 you will be greeted with this nice error message:

editorextensionspackage

The ‘EditorExtensionsPackage’ package did not load correctly. This makes little sense to most people, since it’s not very clear what EditorExtensionsPackage is. Well, it’s the original name of Web Essentials that never got changed as you can see here in the source code.

Download a compatible version

The open source community  around Web Essentials is working on a new release that is compatible with CTP 2 and beyond. It has not yet been released. When it’s released, it will only work on Update 2 CTP 2 and later. Because of the API changes, we haven’t been able to make it backwards compatible, unfortunately.

So, if you want to give CTP 2 a spin, then please try out the new nightly builds of Web Essentials. As an added benefit, the latest build also contains Intellisense improvements to the JavaScript editor as well as support for JsDoc comments.

Download Visual Studio Update 2 CTP 2
Download Web Essentials 2013 for CTP 2

I have yet to see any issues with the latest build of Web Essentials. Actually, several existing bugs have been fixed in that build.

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.