Static File Caching And Cache-busting In ASP.NET Core 1.0 MVC 6

Caching

This is my English version of my original article in Dutch which can be found here. When developing web applications one of the things you will want to do first to improve performance is to cache static files [1] like images and script files and only retrieve them from the server when these files have actually been modified.

There are a number of situations in which a browser needs to check whether a cached entry is valid [2]:

  • The cached entry has no expiration date and the content is being accessed for the first time in a browser session
  • The cached entry has an expiration date but it has expired
  • The user has requested a page update by clicking the Refresh button or pressing F5

Side note: When you start testing the caching of static files, please pay close attention to the last point. Make a link which links back to the page itself instead of refreshing or hitting F5, because else the caching won’t work.

As you could’ve read in the first two situations cached entries can have an expiration date. This used to actually be a date-time stamp in the HTTP header, but nowadays the “cache-control” header “max-age” is much more in use, because it allows you to set an interval [3]. You don’t need to use an explicit expiration date anymore. An interval is much more dynamic. Plus the use of max-age is prefered by modern browsers. So let’s use it!

Side note: According to specifications you shouldn’t set max-age to more than one year.

To be able to set the max-age in the “cache-control” header you first need to add the Static Files Middleware. Middleware deserves its own article and is not the focus of this one. In an ASP.NET Core 1.0 application this is done by adding the following line in the Configure method in the Startup class:

app.UseStaticFiles();

Listing 1: Add Static Files middleware to the HTTP request pipeline

Once you’ve set this up you need to set max-age in the response headers. The UseStaticFiles method accepts StaticFilesOptions. This class has an OnPrepareResponse property [4] which you can use to add or change the response headers. It expects a method that has a single parameter and does not return a value. It can be used to set the max age as follows:

            app.UseStaticFiles(new StaticFileOptions()
            {
                OnPrepareResponse = (context) =>
                {
                    var headers = context.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new CacheControlHeaderValue()
                    {
                        MaxAge = TimeSpan.FromSeconds(60),

                    };
                }
            });

Listing 2: Changing the response header via OnPrepareResponse

As you can see MaxAge expects a TimeSpan which makes it really easy to set it to any interval you want. In this case I’ve just set it to 60 seconds for testing purposes. For production it would be more likely you’ll want to set this value to a year or something. We can check the HTTP headers in for instance the developer tools of Chrome:

200OKfromcache
Figure 1: max-age = 60 and Status Code = 200 OK (from cache)

By examining the headers I can see that the max-age has indeed been set to 60 seconds in the Cache-Control response header and when I retrieved the page again by clicking a link that references the same page (no refresh or F5) I can see that Status Code is 200 OK (from cache). When after 60 seconds I click that same link again I get Status Code 304 Not Modified. Excellent, the time interval has expired and the browser checks the server with a very small request to only ask if the file has been modified. The server answered with a tiny response ‘304 Not Modified’ claiming the file hasn’t been modified:

304NotModified
Figure 2: Status Code = 304 Not Modified

So the large .jpg file didn’t need to be transferred across the wire. This is all coming along nicely right? But what about files that have been modified within the interval? Now we need to talk about Cache-busting.

Cache-busting

Cache-busting is basically preventing the browser to serve a static file from its cache [5]. MVC 6 has a Tag Helper [6] called “asp-append-version” which can be used on HTML tags which involve static files, like the “img” HTML tag. What it does when you set its value to “true” is basically utilizing a common way to force cache-busting, which is by versioning the static file. The Request URL is appended with “?v=o90nYT80kXYsi_vP1o1mIgqmQzrL3QV-ADZlxN_M9_8” by this Tag Helper. What you see here is a query string (hence the ?) with the parameter v (which stands for version) and a hash code after the equal sign (=) which was computed by the server based on the contents of the static file which was being served. So for example:

<img src="~/img/Ahi99Y3LN6EJRBeUu5NB49MAiG_42tnVdeRrYtvr5qNu.jpg" asp-append-version="true" />

The browser will cache the static file with this modified request URL as a reference. When the content of the file is changed the server will compute a new hash code based on the new content and the request URL will actually change for the browser, ‘tricking’ the browser into retrieving the static file from the server instead of from its cache. Let’s see this in action:

200OKFromCache_2
Figure 3: Status Code = 200 OK (from cache) with static file versioning
If we click the link within the interval of max-age we see status code 200 OK, but “(from cache)”! Now look at “Request URL” in figure 3, you can see the versioning of the .jpg file in action.

After the interval from max-age, the browser behaves as expected and needs to ask the server if anything has been modified:
304NotModified_2
Figure 4: Status Code = 304 Not Modified with static file versioning

But what if we are within the max-age interval and the static file is changed? This would mean in this example that the image has been altered. Well, let me show you:
200OK
Figure 5: Status Code = 200 OK with static file versioning

Please compare the Request URL from this screenshot to the Request URL of the screenshot in figure 4. Notice how the hash code is completely different. The browser bypassed its cache and went to the server to retrieve a ‘new’ static file, because to the browser the link is different.

Side note: I got this result by clicking the link to the page itself again, making sure I got a 200 status code OK (from cache). Then I quickly opened the image in Paint and added a line, saved it, and closed it. Then I clicked the link again. Word to the wise, increase your max-age to an hour or something, instead of 60 seconds.

Conclusion

The ease with which you can hook into the HTTP request pipeline in ASP.NET Core 1.0 and add or change the response headers is fantastic. It allows us to set the max-age with minimum effort, enabling the browser’s native capability to cache static files. Then the help you get from MVC 6’s Tag Helper ‘asp-append-version’ to be able to bust that cache when a static file has been modified is really awesome. Once set up you no longer as a developer need to worry about all the low level details and can start creating awesome web applications for your clients!

Links

  1. Static files: https://docs.asp.net/en/latest/fundamentals/static-files.html
  2. Great site about HTTP and caching: http://www.httpwatch.com/httpgallery/caching/
  3. Another great source on HTTP cache headers: http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/
  4. OnPrepareResponse: https://msdn.microsoft.com/en-us/library/microsoft.owin.staticfiles.staticfileoptions.onprepareresponse%28v=vs.113%29.aspx#P:Microsoft.Owin.StaticFiles.StaticFileOptions.OnPrepareResponse
  5. Definition cache-busting: http://digitalmarketing-glossary.com/What-is-Cache-busting-definition
  6. MVC6 and Tag Helpers: https://docs.asp.net/projects/mvc/en/latest/views/tag-helpers/index.html
Advertisements

About Danny

Bachelor in Commercial ICT MCTS Winforms .NET 2.0 MCTS ASP.NET 3.5 PSM I
This entry was posted in ASP.NET Core 1.0 and tagged . Bookmark the permalink.

2 Responses to Static File Caching And Cache-busting In ASP.NET Core 1.0 MVC 6

  1. Karoly says:

    Hi,

    Great article, thank you.
    One question: how does ‘Cache-busting’ affect the performance of the web app?

    Thanks,
    K.

    • Danny says:

      Thank you.

      Not sure how I should read your question. If I read your question literally, I’d answer: Cache-busting affects performance negatively. If the browser can serve the static file from cache it’ll always be faster than serving it across the wire. But if I really try to understand your question I do hope you understand what cache-busting is. It’s basically necessary to serve a changed static file across the wire. Else it’ll never be updated and always served from cache.

      Cheers,

      Danny

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s