This article shows you how to configure the ASP.NET Core 2.0 MVC File action result to write file to response with cache headers.
You typically work with one of the following action results when you want to write a file to the response.
- FileContentResult(writes a binary file to response)
- FileStreamResult(writes a file from a stream to the response)
- VirtualFileResult(writes a file specified using a virtual path to the response)
These are all coming from ASP.NET Core’s MVC package. In an earlier version of ASP.NET Core (1.*); using these action results, you could have served files but there wasn’t any way of configuring cache headers for the responses.
I’m pretty sure that you are familiar with StaticFiles middleware of the framework. All it does is to serve static files (CSS, JavaScript, image, etc.) from a predefined/configurable file location (typically from inside the web root, i.e., wwwroot folder). But along the way, it also does some cache headers configuration. The reason behind this is that you don’t want your server to be called up every time for a file that doesn’t change frequently. Let’s see how a request for a static file looks like in the browser’s network tab.

So, when the middleware starts processing the request, it also adds a Etag and a Last-Modified header with it before sending it to the browser as a response. Now, if we request the same file again, the browser will directly serve the file from its cache rather than making a full request to the server.

On a subsequent request like above, the server validates the integrity of the cache by comparing the If-Modified-Since and If-None-Match header values with the previously sent Etag and Last-Modified header values respectively. If it matches, that means our cache is still valid and the server sends a "304 Not Modified" status to the browser. On the browser’s end, it handles the status by serving up the static file from its cache.
You may be wondering, in order to make sure whether the response is cached or not, why I’m calling the server again to give me a "304 Not Modified" status. So how does it benefit me? Well, although it’s a real HTTP request to the server, the request payload is much less than a full body request. This means that once the request passes cache validation checks, it is opted out of any further processing. (the processing tunnel may include an access to the database or whatever)
You can add additional headers when serving up static files by tweaking the middleware configuration. For example, the following setting adds a Cache-Control header to the response which notifies the browser that it should store the cached response for 10 minutes (max-age=600) only. The public field means that the cache can be stored in any private (user specific client/browser) cache store or some in between cache server or on the server itself (memory cache). The max-age field also specified that when the age of the cache is expired, a full body request to server should be made. Now your concern would be what about the Etag and Last-Modifiedheaders validation. Well when you specify a Cache-Control with a max-age the Etag and Last-Modifiedheaders just goes down into the priority level. By default, the StaticFiles middleware is configured to have a max-age of a lifetime.
- app.UseStaticFiles(new StaticFileOptions()
- {
- OnPrepareResponse = ctx => {
- ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=600");
- }
- });
But enough about static files that are publicly accessible. What about a file we intended to serve from an MVC action? How do we add cache headers to those?
Well, it wasn’t possible until in the recent version of the ASP.NET Core 2.0 (Preview 2). The action results, I pointed out at the very beginning of the post are now overloaded with new parameters such as,





Join the conversation! Your thoughts help the community grow.