Thanks to the ASP. NET Core middleware pipeline, it's relatively simple-to-add additional HTTP headers to your application By using custom middleware. One common use case for this is to add caching headers.
Allowing clients and CDNs to cache your content can has a massive effect on your application ' s performance. By allowing caching, your application never sees these additional requests and never have to allocate resources to process them, so it's more available for requests this cannot be cached.
In most cases you'll find that a significant proportion of the requests to your site can is cached. A Typical site serves both dynamically generated content (e.g. in ASP., the HTML generated by your Razor templates ) and static files (CSS stylesheets, JS, images etc). The static files is typically fixed at the time of publish, and so is perfect candidates for caching.
In this post I'll show how can add headers to the files served by the StaticFileMiddleware
increase your site ' s performance. I'll also show how can add a version tag to your file links, to ensure you don ' t inadvertently serve stale data.
Note that this isn't the only-the-headers to-add cache to your site. You can also use the on ResponseCacheAttribute
MVC to decorate Controllers and Actions if you are returning data which was safe to cache.
could also consider adding caching at the reverse proxy level (e.g. in IIS or Nginx), or use a third party provider Li Ke CloudFlare.
Adding Caching to the Staticfilemiddleware
When you create a new ASP. NET Core project from the default template, you'll find the is StaticFileMiddleware
added early in the middle Ware pipeline, with a call to AddStaticFiles()
in Startup.Configure()
:
PublicvoidConfigure(Iapplicationbuilder app){Logging and exception handler removed for clarity app.Usestaticfilesusemvc (routes => {routes.maproute" default ": }) ;
This enables serving files from the wwwroot folder in your application. The default template contains a number of static files (site.css, bootstrap.css, banner1.svg) w Hich is all served by the middleware while running in development mode. It's These we wish to cache.
Don ' t we get caching by default?
Before we get to adding caching, lets investigate the default behaviour. The first time you load your application, your browser'll fetch the default page, and would download all the linked asset S. Assuming everything is configured correctly, these should all return a 200 - OK
response with the file data:
As well as the file data, by default the response header would contain ETag
and Last-Modified
values:
HTTP/1.1 200 OK Date: Sat, 15 Oct 2016 14:15:52 GMT Content-Type: image/svg+xml Last-Modified: Sat, 15 Oct 2016 13:43:34 GMT Accept-Ranges: bytes ETag: "1d226ea1f827703" Server: Kestrel
The second time a resource is requested from your site, your browser would send this and value in the ETag
Last-Modified
header as If-None-Match
and If-Modified-Since
. This tells the server so it doesn ' t need to send the data again if the file hasn ' t changed. If it hasn ' t changed, the server would send a 304 - Not Modified
response, and the browser would use the data it received previously inst ead.
This level of caching comes Out-of-the-box StaticFileMiddleware
with the, and gives improved performance by reducing the amount of BANDWI DTH required. However it was important to note that the client was still sending a request to your server-the response have just been opt Imised. This becomes particularly noticeable with high latency connections or pages with many Files-the browser still have to Wai T for the response to come back as 304
:
The image above uses Chrome ' s built in network throttling to emulate a GPRS connection with a very large latency of 500ms. You can see that the first Index page loads in 1.59s and after which the remaining static files is requested. Even though they all return 304
responses using only bytes, the page doesn ' t actually finish loading until and Addit ional 2.5s has passed!
Adding cache headers to static files
Rather than requiring the browser to always check if a file have changed, we now want it to assume this file is the SAM E, for a predetermined length of time. This is the purpose of the Cache-Control
header.
in ASP. NET Core, you can easily the add this this header when you configure the StaticfileMiddleware
:
Using Microsoft. Net. Http. Headers; app.Usestaticfiles(NewStaticfileoptions{Onprepareresponse= CTX=>{const int durationinseconds = 60 * 60 * 24[headernames = + durationinseconds}}) ;
One UseStaticFiles
of the overloads of takes a StaticFileOptions
parameter, which contains the property OnPrepareResponse
. This action can being used to specify any additional processing, should occur before a response is sent. It is passed a single parameter, a StaticFileResponseContext
, which contains the current and also an property HttpContext
representing the IFileInfo
cur Rent file.
If set, the is Action<StaticFileResponseContext>
called before each successful response, whether a 200
or 304
response, but it won ' t be called if The file is not found (and instead returns a 404
).
In the example provided above, we is setting the Cache-Control
header (using the constant values defined in MICROSOFT.NET.HTT P.headers) to the cache our files for hours. You can read up on the details of the various associated cache headers here. In this case, we marked the response as as public
we want intermediate caches between our servers and the user to store the Cached file too.
If We run our high-latency scenario again, we can see our results in action:
Our Index page still takes 1.58s to load, but as can see, all our static files is loaded from the cache, WHI CH means no requests to our server, and consequently no latency! We ' re all do in 1.61s instead of the 4.17s we had previously.
Once the max-age
duration we specified have expired, or after the browser evicts the files from its cache, we'll be back to M Aking requests to the server, but until then we can see a massive improvement. What's more, if we use a CDN or there is intermediate cache servers between the user's browser and our servers, then they Would also is able to serve the cached content, rather than the request have to make it all the the-to your server.
Note: Chrome is a bit funny with respect to cache behaviour-if you reload a page using F5 or the reload button, It would generally not use cached assets. Instead it would pull them down fresh from the server. If you is struggling to see the fruits of your labour, navigate to a different page by clicking a link-you should see T He correct caching behaviour then.
Cache Busting for file changes
Before we added caching we saw that we return an ETag
whenever we serve a static file. This was calculated based on the properties of the file such that if the file changes, the ETag would change. For those interested, this is the snippet of code, which is used in asp:
_length= _fileinfo. Length;D Atetimeoffset Last= _fileinfo. LastModified;Truncate to the Second._lastmodified=NewDateTimeOffset(Last. Year, last. Month, last. Day, last. Hour, last. Minute, last. Second, last. Offset).ToUniversalTime();Long Etaghash= _lastmodified. Tofiletime (^ _ Length= new entitytagheadervalue (+ convert. Tostring+ "
This works great before we add caching-if ETag
the hasn ' t changed we return a 304
, otherwise we return a 200
respon SE with the new data.
Unfortunately, once we add caching, we is no longer making a request to the server. The file could has completely changed or has been deleted entirely, but if the browser doesn ' t ask, the server can ' t tel L them!
One common solution around this was to append a querystring to the URL when you reference the static file in your markup. As the browser determines uniqueness of requests including the QueryString, it treats as https://localhost/css/site.css?v=1
a different file to https://localhost/css/site.css?v=2
. You can use this approach by updating any references to the file in your markup whenever the file.
While the This works, it requires the find every reference to your the static file anywhere on your site whenever your change th e file, so it can is a burden to manage. A simpler technique is to has the querystring being calculated based on the content of the file itself, much like an ETag
. That is, when the file changes, the QueryString'll automatically change.
This isn't a new Technique-mads Kristensen describes one method of achieving it with ASP. NET 4.X here, with ASP. Core We can use the link, script and image Tag Helpers to does the work for us.
It's highly likely that's actually already using these tag helpers, as they is used in the default templates for E xactly this purpose! For example, in _layout.cshtml, you'll find the following link:
<script src= "~/js/site.js" asp-append-version=" True "> </script>
The Tag Helper asp-append-version="true"
is added with the markup and ensures if rendered, the link would be rendered with a hash of the File as a querysting:
<script src="/js/site.js?v=Ynfdc1vuMNOWZfqTj4N3SPcebazoGXiIPgtfE-b2TO4"></script>
If The file changes, the SHA256 hash would also change, and the cache would be automatically bypassed! Can add this Tag Helper img
to, and script
link
elements, though there is obviously a degree of overhead as a hash o f The file has been calculated on first request. For files which is very unlikely to ever change (e.g. some images) it could not be worth the overhead to add the helper, BU T for others it'll no doubt prevent quirky behaviour once you add caching!
Summary
In this post we saw the built in caching using ETag
s provided out of the box with the StaticFileMiddleware
. I then showed how to add caching to the requests to prevent unnecessary requests to the server. Finally, I showed how to breaks out of the cache when the file changes, by using tags Helpers to add a version querystring T o The file request.
Adding Cache-control headers to Static Files in ASP