Table of Contents
Every seasoned web developer experienced at least once some cache-related issue with static files. One of the most common scenarios is the following: you publish a new version of your web application that seems to work well, then - despite all your debug and tests - your friends, colleagues and/or users are unable to see the new stuff due to the fact that some old CSS or JS file is being still used by their client, despite you are 100% sure you replaced it on the server with a new version.
How can it even be possible? You ask to yourself for a split second, then you quickly realize that you're hitting one of the simplest, yet most annoying issues of all time: the mere existence of a browser's and/or proxy's cache which is still holding the previous version of your updated file.
Until the last few years, this issue was mostly related to CSS and JS files only: however, with the increasing popularity of Single Page Applications and SPA-oriented frameworks such as ReactJS, AngularJS and Angular2, is now also affecting the index.html page of these kind of apps.
Using ASP.NET 4 (and below)
If you're using ASP.NET 4 or earlier, the issue can be easily overcome by adding some lines to the application’s web.config file, such as the following:
1 2 3 4 5 6 7 8 9 10 11 |
<caching enabled="false" /> <staticContent> <clientCache cacheControlMode="DisableCache" /> </staticContent> <httpProtocol> <customHeaders> <add name="Cache-Control" value="no-cache, no-store" /> <add name="Pragma" value="no-cache" /> <add name="Expires" value="-1" /> </customHeaders> </httpProtocol> |
Placing this inside the <system.webServer> node is all what you need to get disable all kind of client-side caching. However, if you're using ASP.NET Core, you won't be able to pull this out.
Using ASP.NET Core
As you might already know, the new ASP.NET Core’s configuration system has been re-architected from scratch: it doesn't depend anymore to XML configuration files such as the web.config, so there's no chance we can use it to control our static files cache.
The new pattern is based upon key/value settings that can be retrieved from a variety of sources, including Json files (such as the appsettings.json file): once retrieved, they can be accessed within our code programmatically, using a technique not too different from what we could do with the old System.Configuration namespace.
Initial check
That said, the first thing to do is to make sure that our ASP.NET Core application is loading the appsettings.json file. Open the Startup.cs file and check if class costructor contains the following lines of code or not:
1 2 3 4 5 6 7 8 9 |
public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } |
If we started our project using the yeoman generator or any of the ASP.NET Core templates provided by Visual Studio 2015, everything should be already there: otherwise, just add it.
Setting the HTTP Headers for Static Files
Right after that, keep the Startup.cs file open and scroll down until you reach the Configure method and add (or modify) the app.UseStaticFiles() middleware to set a custom caching behaviour just like the following (relevant lines are highlighted):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); // Configure a rewrite rule to auto-lookup for standard default files such as index.html. app.UseDefaultFiles(); // Serve static files (html, css, js, images & more). See also the following URL: // https://docs.asp.net/en/latest/fundamentals/static-files.html for further reference. app.UseStaticFiles(new StaticFileOptions() { OnPrepareResponse = (context) => { // Disable caching for all static files. context.Context.Response.Headers["Cache-Control"] = "no-cache, no-store"; context.Context.Response.Headers["Pragma"] = "no-cache"; context.Context.Response.Headers["Expires"] = "-1"; } }); } |
Adding the appsettings.json file to the loop
Now that we learned how to change the default caching behaviour, we need to change these static values with some convenient references pointing to the appsettings.json file. That way we won't have these settings hard-coded into our application sources and we'll be able to change them using different settings files, such as an appsettings.production.json for production environments.
In order to do that, open the appsettings.json file (create it if it doesn't already exists) and add the following key/value section (relevant lines highlighted):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } }, "StaticFiles": { "Headers": { "Cache-Control": "no-cache, no-store", "Pragma": "no-cache", "Expires": "-1" } } } |
Replacing values with references
Now we just need to replace the literal string values in the Startup.cs file with a reference to these configuration keys. We can do that in the following way (modified lines are highlighted):
1 2 3 4 5 6 7 8 9 10 |
app.UseStaticFiles(new StaticFileOptions() { OnPrepareResponse = (context) => { // Disable caching for all static files. context.Context.Response.Headers["Cache-Control"] = Configuration["StaticFiles:Headers:Cache-Control"]; context.Context.Response.Headers["Pragma"] = Configuration["StaticFiles:Headers:Pragma"]; context.Context.Response.Headers["Expires"] = Configuration["StaticFiles:Headers:Expires"]; } }); |
Learning how to use this new configuration pattern can be very useful, as it’s a great way to customize our web application’s settings.
For further informations regarding this topic, we strongly suggest reading the following great posts from the official ASP.NET Core documentation web site:
That’s it for now: happy cache control!