Cache Rules Everything Around Me : Using ASP.NET MVC6 TagHelpers to Bust Cache
I cannot count how many times in my life as a developer that I have been contacted after a deployment by a client that is unable to see any of the changes that were deployed (or something isn't working). There are a variety of ways to respond to this but it usually involves a process of holding a key and pressing another or going into options.
I'm not terribly fond of this process, but thankfully the latest release of ASP.NET allows me to easily get rid of this issue quite easily through the use of TagHelpers.
Caching and Issues With It
Caching issues generally result from a browser holding onto an older version of a file after it has been changed.
This is usually a good thing for everyone as the server is happy because it doesn't have to serve out a file that it has already served to a client and the client is happy because they experienced zero load-time for the file. It sounds a bit like a win-win scenario, however, when some of these files that were being cached are changed after a release or deployment, things can go horribly, horribly wrong.
Non-cached objects of an application will now be talking to older versions of files, script files may be relying on DOM elements that either no longer exist or simply don't exist yet, CSS rules can change and cause your application to look strange or worse, your application may not simply work at all.
Fixing Caching Issues
Caching issues are usually resolved at the client-level or more specifically by the end-user. The following is an exchange that you may have taken part in previously or just an example of one that could occur:
- Developer: "Well, we deployed the update this morning. Everything should be working along with all of the new features."
- Client: "I just tried accessing it and everything looks funny, besides that, nothing seems to work."
- Developer: "You may need to clear your cache for the changes to take effect."
- Client: "Cache? Haven't I paid you folks enough already?"
- Developer: "No, the cache in your browser. It's storing the older files prior to the update and you need to get rid of them."
- Client: "Well how do I do that?"
- Developer: "You can either
go into Options within your browser and choose the Clear Temporary Files option
orhold the CTRL key and press F5
. You should see a flicker and then it should work." - Client: "Yep. I just did that and it seemed to fix it. Now do I need to do this every time that I open the application or site?"
The issue with this scenario is that it should not be the responsibility of the end user to have to clear their cache every time something happens.
How Caching Can Mess You Up
Files are cached in different configurable ways, but the most common case where a file is being cached unnecessarily is also the most common way the file is referenced.
Let's take the popular client-side library jQuery for example and assume we are referencing it within our web application :
<script src="~/Scripts/jquery.js"></script>
There isn't really anything wrong with this - and if caching is enabled after the first time it is requested, the jquery.js file will be stored locally within the cache and will not be requested from the server again. As I mentioned earlier, this is great as it reduces overhead to the server and will speed up load times since the file is being accessed locally.
The problem comes in when the developer decides to upgrade the version of jQuery and we aren't generally talking about minor updates which will not introduce major breaking changes, but let's use the leap from jQuery 1.x to jQuery 2.x. This release changed all sorts of APIs, removed support for older browsers and did quite a few other things that could break an application.
Now if the user has the previous version cached and you revisit the application after changing quite a bit of your markup, the newer markup should be picked up, but the older jQuery file is still going to be used, which is not what we want.
Removing Caching Entirely
One of the most common solutions to get around this is to simply remove caching entirely. This is usually done by appending a randomly generated querystring parameter that uses something like a timestamp as seen below :
<!-- This might render something like "~/Scripts/jquery.js?dt=123456789" -->
<script src="~/Scripts/jquery.js?dt=@(DateTime.Now.Ticks)"></script>
This will append a timestamp to the end of your jQuery request each time. The server uses the querystring parameters to differentiate between individual requests and thus it would load your jQuery file from the server every time.
Sure, this resolves the issues of always serving the user the proper file, but it is terribly inefficient. It negates all of the benefits of caching and simply results in longer load times and additional server overhead.
"Cache-busting" : A Versioned Approach
A more effective version of the previous approach might be to append a version number of your application as a querystring parameter. This would ensure that all of your resources would contain the version number of the current release similar to the following example :
<!-- This might render something like "~/Scripts/jquery.js?v=1.1" -->
<script src="~/Scripts/jquery.js?v=@(YourApplicationVersionNumber)"></script>
This is commonly referred to as "cache-busting" and it's a nice balance to strike when deploying new versions of an application. Basically, once a release is pushed out, you'll simply need to update the version number for your application and it will automatically pull the latest versions from the server and cache them (ignoring the older versions entirely).
Using TagHelpers to Cache-bust
One of the most interesting features of ASP.NET MVC6 is the introduction of TagHelpers. Now this post isn't a crash course on them, but if you want to learn a bit more, I'd recommend reading through Jeff Fritz's blog post on them here and this MSDN article on them.
In a nutshell, TagHelpers allow developers to use a bit of magic within their ASP.NET MVC Views and are really designed to be cleaner, better versions of the existing HtmlHelpers that are used today.
With regards to cache-busting, there is going to be one specific attribute that we will be discussing, and that is the asp-append-version
attribute. With the use of TagHelpers, simply appending this attribute will function similar to the versioned cache-busting approach mentioned above.
You'll now just need to use the following to ensure the proper version of your file is used
<!-- This might render something like "~/Scripts/jquery.js?v=is_TDV8iLgND2WN04JRVo613wYNCbRcGd_y8caHTLZE" -->
<script asp-append-version="true" src="~/Scripts/jquery.js"></script>
That's it. Simply append asp-append-version="true"
to any <script>
,<link>
or <style>
tag and it should add a querystring value that corresponds to a specific version of that file.
What's great about this?
- It's easy. There's no server-side code to add or values to adjust, simply append a single attribute and it works.
- It's automatic. You don't have to worry about manually updating a version for your project, since the version is based off of the file itself, .NET handles all of that for you.
- No more calls. Since this handles busting the cache, you don't have to worry about clients calling complaining that "things look weird" as this will ensure they are served the proper file.
- It's smart. Another benefit that this approach offers over some of the alternatives is that it will only bust the cache for files that need it. If your
site.css
file didn't change since the last deployment, it should still be cached and its version number shouldn't change.
What do I need to do?
To actually take advantage of the TagHelpers like this one, you don't have to do a whole lot. In fact, you can simply add the following line at the top of any Views that take advantage of the them and they will just work :
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"