Link and Script Tag Helpers in ASP.NET Core MVC

In my last post we explored how to use the environment tag helper to generate different HTML for Development versus Production environments in ASP.NET MVC6. I very briefly touched on the link and script tag helpers. In this post, we will dig into the details of these very useful tag helpers.

Overview

The link and script tag helpers are intended to make it easier for us to add link and script tags to our HTML. In particular, these tag helpers simplify the process of referencing a large number of files in a development environment, referencing CDNs with local fallbacks and cache busting.

Globbing

No, I’m not making up words. Globbing is a way to specify sets of filename using wildcard patterns. In a very simple example, we could specify all the JavaScript files in a particular folder by specifying the pattern /scripts/*.js. This pattern, however, would not include any of the files in sub-directories. This is where globbing gets more interesting. We can include all JS files in a folder and all subfolders by specifying the pattern /scripts/**/*.js.

That’s nice, but how does this relate to the link and script tag helpers? Well, imagine you are building an web application that consists of a very large number of JavaScript files. When running in production, you will probably want to combine all those files into a single file, but in a development environment it can be convenient to reference each individual file. Remembering to add a new script tag every time you add a new JavaScript file can be tedious and error prone. The script tag helper lets you specify glob patterns for all the files that you want to include.

We could include all the individual script files above using a single script tag helper as follows:

<script asp-src-include="~/app/**/*.js"></script>

Which will generate the following HTML:

<script src="/app/app.js"></script>
<script src="/app/controllers/controller1.js"></script>
<script src="/app/controllers/controller2.js"></script>
<script src="/app/controllers/controller3.js"></script>
<script src="/app/controllers/controller4.js"></script>
<script src="/app/services/service1.js"></script>
<script src="/app/services/service2.js"></script>

There is also src-exclude attribute if there were a set of files you wanted to exclude from the list. For example, to exclude the services scripts in the example above, we would use the following:

<script asp-src-include="~/app/**/*.js" 
asp-src-exclude="~/app/services/**/*.js">
</script>

The nice thing about using a glob pattern is that we don’t need to remember to update our HTML every time we add or remove a JavaScript file to our application.

The link tag helper works in exactly the same way with the asp-href-include and asp-href-exclude attributes.

CDNs and Fallbacks

It is a common approach to reference popular frameworks from hosted CDN in order to reduce network load on your server and potentially improve performance for the end user. For popular frameworks like jQuery and Bootstrap, there is a good chance that the browser will already have a cached version of these files.

Referencing files from a CDN can be a bit of a pain because you also need to provide a fallback to a version of the file hosted on your servers. A fallback is necessary because you do not want your application to go down just because the CDN is not currently reachable.

The script and link tag helpers make it easier to specify a fallback test and file location. Here is a basic example for the popular Bootstrap framework:

<link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css"
asp-fallback-test-class="hidden"
asp-fallback-test-property="visibility"
asp-fallback-test-value="hidden" />


<script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js"
asp-fallback-test="window.jQuery">
</script>

Which is infinitely cleaner than manually writing the following HTML that gets generated by the tag helpers:

<link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css" />
<meta name="x-stylesheet-fallback-test" class="hidden" />
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&amp;&amp;e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&amp;&amp;h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/lib\/bootstrap\/css\/bootstrap.min.css"]);</script>

<script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js">
</script>

<script>(typeof($.fn.modal) === 'undefined'||document.write("<script src=\"\/lib\/bootstrap\/js\/bootstrap.min.js\"><\/script>"));</script>

Cache Busting

Cache busting is the process of appending some form of file version hash to the filename of resources like JavaScript and CSS files. The performance advantage of doing this is that we can tell the browser to cache these files indefinitely without worrying about the client not getting the latest version when the file changes. Since the name of the resource changes when the file contents change, the updated files are always downloaded. Mads Kristensen has a really good overview of cache busting in previous versions of ASP.NET. Luckily, the new link and script tag helpers make this MUCH easier.

To enabling cache busting, just set the asp-append-version attribute to true.

<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true"/>

Which will generate a link tag in HTML with a file version query parameter appended:

<link rel="stylesheet" href="/css/site.min.css?v=UdxKHVNJA5vb1EsG9O9uURFDfEE3j1E3DgwL6NiDGMc" />

The process is identical for the script tag helper.

Conclusion

The new link and script tag helpers make the process of including multiple files, referencing CDN hosted resources and cache busting extremely simple.

August 29, 2015 – Updated to align with new renamed attributes in Beta 6 and newer