Caching static resources like stylesheet, JavaScript, or image files allows improving the performance of your website. On the client-side, a static file will always be loaded from the cache which reduces the number of requests to the server and so, the time to get the page and its resources. On the server-side, as they are fewer requests, the server can handle more clients without upgrading the hardware.

While caching is a great thing, you must ensure the client is always running the latest version of the application. I mean, when you deploy the next version of the website, you don’t want the client to use an obsolete cached version of a file.

Versioned URL (Cache buster)

To ensure the user always uses the latest version of a file, we must have a unique URL per version of a file. There are many strategies:

  • Use the query string: https://sample.com/file.js?v=123
  • Rename the file: https://sample.com/file.123.js
  • Create a directory: https://sample.com/123/file.js

ASP.NET Core provides a mechanism using a TagHelper to append the version with the query string. It supports the most common HTML tags that target a static resource: scriptlink and img. All you have to do is append asp-append-version="true" to the tag:

<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<script src="~/js/site.js" asp-append-version="true"></script>
<img src="~/images/banner1.svg" asp-append-version="true" />

This will produce:

<link rel="stylesheet" href="/css/site.css?v=1wp5zz4e-mOPFx4X2O8seW_DmUtePn5xFJk1vB7JKRc" />
<script src="/js/site.js?v=EWaMeWsJBYWmL2g_KkgXZQ5nPe-a3Ichp0LEgzXczKo"></script>
<img src="/images/banner1.svg?v=GaE_EmkeBf-yBbrJ26lpkGd4jkOSh1eVKJaNOw9I4uk" />

The version is the SHA256 of the file encoded in base64. Of course, the hash is computed only once per file and stored in an IMemoryCache.

The URL of files are now unique and will change when the file change, so we can add a cache header to the response to indicate the client that the file can be stored in cache forever.

Response headers

To indicate the browser to store the file in its cache, we must send the Cache-control header and the Expires header for HTTP/1.0 compatibility. To add these headers, we use the OnPrepareResponse callback of the StaticFilesOptions. Let’s modify the Startup.cs file:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = context =>
        {
            // Cache static file for 1 year
            if (!string.IsNullOrEmpty(context.Context.Request.Query["v"]))
            {
                context.Context.Response.Headers.Add("cache-control", new[] { "public,max-age=31536000" });
                context.Context.Response.Headers.Add("Expires", new[] { DateTime.UtcNow.AddYears(1).ToString("R") }); // Format RFC1123
            }
        }
    });

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

You can check the headers are sent by using the Developer Console:

Conclusion

Using HTTP caching is important for performance reasons (client and server sides). With ASP.NET Core, you can take advantage of the provided TagHelpers to generate a versioned URL, and change the default configuration of the StaticFilesMiddleware to add the Cache-control header for these URLs.

Leave a comment

Your email address will not be published. Required fields are marked *