Adding Cache-Control headers to Static Files in ASP.NET Core
Thanks to the ASP.NET Core middleware pipeline, it is 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 have a massive effect on your application's performance. By allowing caching, your application never sees these additional requests and never has to allocate resources to process them, so it is more available for requests that cannot be cached.
In most cases you will find that a significant proportion of the requests to your site can be cached. A typical site serves both dynamically generated content (e.g. in ASP.NET Core, the HTML generated by your Razor templates) and static files (CSS stylesheets, JS, images etc). The static files are typically fixed at the time of publish, and so are perfect candidates for caching.
In this post I'll show how you can add headers to the files served by the StaticFileMiddleware to increase your site's performance. I'll also show how you can add a version tag to your file links, to ensure you don't inadvertently serve stale data.
Note that this is not the only way to add cache headers to your site. You can also use the ResponseCacheAttribute in MVC to decorate Controllers and Actions if you are returning data which is safe to cache.
You could also consider adding caching at the reverse proxy level (e.g. in IIS or Nginx), or use a third party provider like CloudFlare.
Adding Caching to the StaticFileMiddleware
When you create a new ASP.NET Core project from the default template, you will find the StaticFileMiddleware is added early in the middleware pipeline, with a call to AddStaticFiles() in Startup.Configure():
public void Configure(IApplicationBuilder app)
{
// logging and exception handler removed for clarity
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
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) which are all served by the middleware when running in development mode. It is 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 will fetch the default page, and will download all the linked assets. 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 will 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 will send this ETag and Last-Modified value in the header as If-None-Match and If-Modified-Since. This tells the server that it doesn't need to send the data again if the file hasn't changed. If it hasn't changed, the server will send a 304 - Not Modified response, and the browser will use the data it received previously instead.
This level of caching comes out-of-the-box with the StaticFileMiddleware, and gives improved performance by reducing the amount of bandwidth required. However it is important to note that the client is still sending a request to your server - the response has just been optimised. This becomes particularly noticeable with high latency connections or pages with many files - the browser still has to wait 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, after which the remaining static files are requested. Even though they all return 304 responses using only 250 bytes, the page doesn't actually finish loading until and additional 2.5s have passed!
Adding cache headers to static files
Rather than requiring the browser to always check if a file has changed, we now want it to assume that the file is the same, for a predetermined length of time. This is the purpose of the Cache-Control header.
In ASP.NET Core, you can easily add this this header when you configure the StaticfileMiddleware:
using Microsoft.Net.Http.Headers;
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
const int durationInSeconds = 60 * 60 * 24;
ctx.Context.Response.Headers[HeaderNames.CacheControl] =
"public,max-age=" + durationInSeconds;
}
});
One of the overloads of UseStaticFiles takes a StaticFileOptions parameter, which contains the property OnPrepareResponse. This action can be used to specify any additional processing that should occur before a response is sent. It is passed a single parameter, a StaticFileResponseContext, which contains the current HttpContext and also an IFileInfo property representing the current file.
If set, the Action<StaticFileResponseContext> is called before each successful response, whether a 200 or 304 response, but it won't be called if the file was not found (and instead returns a 404).
In the example provided above, we are setting the Cache-Control header (using the constant values defined in Microsoft.Net.Http.Headers) to cache our files for 24 hours. You can read up on the details of the various associated cache headers here. In this case, we marked the response as public as we want intermediate caches between our server 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 you can see, all our static files are loaded from the cache, which means no requests to our server, and consequently no latency! We're all done in 1.61s instead of the 4.17s we had previously.
Once the max-age duration we specified has expired, or after the browser evicts the files from its cache, we'll be back to making requests to the server, but until then we can see a massive improvement. What's more, if we use a CDN or there are intermediate cache servers between the user's browser and our server, then they will also be able to serve the cached content, rather than the request having to make it all the way 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 will generally not use cached assets. Instead it will pull them down fresh from the server. If you are struggling to see the fruits of your labour, navigate to a different page by clicking a link - you should see the 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 is calculated based on the properties of the file such that if the file changes, the ETag will change. For those interested, this is the snippet of code that is used in ASP.NET Core:
_length = _fileInfo.Length;
DateTimeOffset last = _fileInfo.LastModified;
// Truncate to the second.
_lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset).ToUniversalTime();
long etagHash = _lastModified.ToFileTime() ^ _length;
_etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, 16) + '\"');
This works great before we add caching - if the ETag hasn't changed we return a 304, otherwise we return a 200 response with the new data.
Unfortunately, once we add caching, we are no longer making a request to the server. The file could have completely changed or have been deleted entirely, but if the browser doesn't ask, the server can't tell them!
One common solution around this is 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 https://localhost/css/site.css?v=1 as 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 you change the file.
While this works, it requires you to find every reference to your static file anywhere on your site whenever you change the file, so it can be a burden to manage. A simpler technique is to have the querystring be calculated based on the content of the file itself, much like an ETag. That way, when the file changes, the querystring will automatically change.
This is not a new technique - Mads Kristensen describes one method of achieving it with ASP.NET 4.X here but with ASP.NET Core we can use the link, script and image Tag Helpers to do the work for us.
It is highly likely that you are actually already using these tag helpers, as they are used in the default templates for exactly this purpose! For example, in _Layout.cshtml, you will find the following link:
<script src="~/js/site.js" asp-append-version="true"></script>
The Tag Helper is added with the markup asp-append-version="true" and ensures that when rendered, the link will 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 will also change, and the cache will be automatically bypassed! You can add this Tag Helper to img, script and link elements, though there is obviously a degree of overhead as a hash of the file has to be calculated on first request. For files which are very unlikely to ever change (e.g. some images) it may not be worth the overhead to add the helper, but for others it will no doubt prevent quirky behaviour once you add caching!
Summary
In this post we saw the built in caching using ETags 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 break out of the cache when the file changes, by using Tag Helpers to add a version querystring to the file request.
Adding Cache-Control headers to Static Files in ASP.NET Core的更多相关文章
- Upload Files In ASP.NET Core 1.0 (Form POST And JQuery Ajax)
Uploading files is a common requirement in web applications. In ASP.NET Core 1.0 uploading files and ...
- Static Resources In ASP.NET Core 1.0
静态资源包括HTML,CSS,图片和Js文件.在ASP.NET Core 1.0中,静态资源默认的所在目录是wwwroot,wwwroot可以在project.json中定义. Steps: 在www ...
- [转]ASP.NET Core: Static Files cache control using HTTP Headers
本文转自:https://www.ryadel.com/en/asp-net-core-static-files-cache-control-using-http-headers/ Every sea ...
- 前端技巧:禁止浏览器static files缓存篇(转)
前端技巧:禁止浏览器static files缓存篇 由于CSS/JS文件经常需要改动,前端调试时是不希望浏览器缓存这些文件的. 本文记录博主的经验. Meta法 目前在chrome调试还没有遇到问题, ...
- [Hapi.js] Serving static files
hapi does not support serving static files out of the box. Instead it relies on a module called Iner ...
- Django App(五) load static files
经过前面4篇的努力,已经基本完成了,polls站点的功能,但是所有界面都没有涉及样式,和JavaScript的导入.到目前为止了解到的Django是通过解析Url来完成对客户端的响应的,那么组成站点所 ...
- Adding Digital control to Dual tracking LM317 / LM337 Bench supply
Adding Digital control to Dual tracking LM317 / LM337 Bench supply I've been working on my own idea ...
- Settings Django Static Files
静态文件是通过django.contrib.staticfiles来管理的. 配置Django静态文件,Djang官网静态文件配置介绍.简言之,通过以下三个步骤来配置和加载静态文件: 设置静态文件别名 ...
- 网站 cache control 最佳实践
推荐阅读: 2020年软件开发趋势 高并发案例 - 库存超发问题 负载均衡的分类及算法 异地多活架构 Postman 的替代品来了 有时,当第二次访问网站时,看起来比较怪,样式不正常. 通常,是因为 ...
随机推荐
- Flask - 第一篇
首先,要看你学没学过Django 如果学过Django 的同学,请从头看到尾,如果没有学过Django的同学,并且不想学习Django的同学,轻饶过第一部分 一. Python 现阶段三大主流Web框 ...
- 1、svn架设、基本命令
SVN是Subversion的简称,是一个开放源代码的版本控制系统.是一项十分基础,必须能够熟练使用的工具.Apache网站:https://subversion.apache.org/ 采用C/S模 ...
- jQuery自动触发事件
转自:https://blog.csdn.net/CY_LH/article/details/78982218 常用模拟 有时候,需要通过模拟用户操作,来达到单击的效果.例如在用户进入页面后,就触发c ...
- RocketMQ初探(一)
初学RocketMQ,认识一门新技术,还是哪三问:是什么?能干什么?怎么用? 消息中间件主要是实现分布式系统中解耦.异步消息.流量销锋.日志处理等场景. Rocketmq是阿里捐赠给Apache的.3 ...
- Keepalived 角色选举
简介: 1.在 Keepalived 集群中,其实并没有严格意思上的主.备节点,虽然可以在 keepalived.conf 中定义 state 选项为 MASTER 状态,但是这并不意味着此节点就一直 ...
- EF CodeFirst简单实例
运行环境:VS2012,添加的EntityFramework为6.0.2 版本不用太关心,只要知道原理就行了: 基本代码就这几行: namespace ConsoleApplication1 { ...
- 一道容易栽坑的有趣的面试题(关于js,定时器,闭包等)
1.首先下面代码输出什么? for (var i = 0; i < 5; i++) { console.log(i); } 输出:0 1 2 3 4 2.上面只是普通的输出,没有陷阱再看下面这个 ...
- cdoj1091-秋实大哥の恋爱物语 【kmp】
http://acm.uestc.edu.cn/#/problem/show/1091 秋实大哥の恋爱物语 Time Limit: 5000/2000MS (Java/Others) Memo ...
- Unity3d 下websocket的使用
今天介绍一下如何在Unity3D下使用WebSocket. 首先介绍一下什么是websocket,以及与socket,和http的区别与联系,然后介绍一下websocket的一些开源的项目. WebS ...
- adf错误
1>无法验证事务处理中的所有行 运行项目报错: javax.faces.el.EvaluationException: oracle.jbo.TxnValException: JBO-27023 ...