Advanced Cache-busting with Single-Page-Apps and .NET Framework

Busting cache in MVC is pretty easy stuff — just leverage .NET MVC bundling to detect changes and append a unique identifier to your bundles.

Things get trickier when users aren’t always refreshing though. Consider the following scenario:

  1. Frank visits FStop.fm and downloads the FStop v1.0 SPA from your site to his browser. He uses it nonstop without refreshing his browser because your app is awesome.
  2. You ship an update, FStop v1.1.
  3. Frank hasn’t refreshed his browser in a year because he’s been glued to FStop.
  4. Frank’s v1.0 client-side code makes a request to v1.1 API endpoints, which are delivering v1.1 payloads. Frank’s app errors out or crashes.

How do you prevent this from happening? It turns out it’s not so bad. We need some way for your SPA to know what version of client-side code it is. Then, we need to append the version number to AJAX responses that our API returns. Finally, we need to compare the two at every response on the client, and if they’re different, advise Frank that it’s in his best interest to reload when he has the chance.

Part 1: What version do I have on the browser?

Your code needs to know what version it is. So, as part of every initial payload that gets sent down with your SPA, append the BuildVersion:

// C# ViewModel 
viewModel.VersionNumber = Assembly.GetExecutingAssembly().GetName().Version.ToString();

Then serialize this into your app’s context when it hits the browser on your Index.cshtml:

<script type="text/javascript"> 
window.appContext = @Html.Raw(JsonConvert.SerializeObject(Model))
</script>

Now your clientside viewmodels and services have access to the cached version.

Part 2: How do I tell the client that there’s a new version available?

Now your WebAPI requests need to be decorated with a little versioning. This was achieved by creating a custom Filter and registering it globally:

public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { 
var version = Assembly.GetExecutingAssembly().GetName().Version;
actionExecutedContext.Response.Content.Headers.Add("BuildVersion", version.ToString());
}

Responses from the server will contain this header:

Part 3: How do I decide when to notify the user?

Your app needs to ask this of every subsequent AJAX request it makes by reading the Header value we just created. So in your base AJAX service (or wherever your requests get sent from — hopefully not a bunch of places), implement a check like the following:

if (!warned){
var newVersionNumber = response.getResponseHeader("BuildVersion");
if (newVersionNumber != cachedVersionNumber){
doSomething();
}
}

This means that if the app errors out because of a mismatch, you force a refresh. Otherwise, just tell Frank once that there could be some bugginess if the he doesn’t refresh.


Thanks for reading!

Mick