How to Response Caching in ASP.NET Core

In this article, let's talk extensively about what caching is about and how we can generally induce response caching to our App.

In a previous article, we were looking at how we can serve static files efficiently in an ASP.NET Core MVC environment. In this article, let’s talk a bit extensively about what caching is about and how we can generally induce response caching to our App.

How Caching is useful for an API?

In simple terms, caching means to persist an already produced result for certain amount of time for a variety of benefits. It can help reduce the number of times a client would otherwise request the server for data, it can reduce the load on the server for processing a previously processed and stale responses and so on.

Caching is not just a client paradigm, it happens only by a mutual consent between the client and the server. Think of it in this way, how can the client know for how much time the response it received from the server is valid so that it can skip requesting the server for the same resource again and again when required. It is the responsibility of the server to communicate to the client about how it should handle the response it has received for a future use.

It is the client responsibility to efficiently preserve it for future use till the time it is deemed to be valid. And when the time comes, the client must make the same request again to the server, with some information about what it possess currently.

The server has to respond in such way that if in case the response is still unchanged, it should permit the client to use the already persisting response for a little longer duration.

When you look at this in the whole picture, it improves user experience because now the user need not wait for data loads even if he visits an already visited page. Or the user need not wait for a static asset (like an image or a style-sheet) which seldom changes to be loaded on each page visit.

Instead, we can just show up an already persisted copy (also called a cache copy) of that asset and cater to a better experience. And when the cached copy expires, we can do a fresh GET of the asset which is accepted since it happens just a fewer times now.

Similarly, the user can be now presented with a better offline experience than showing up an Error page when there’s no network to make API calls. These are some of the use cases of an efficient cache managed applications.

When it comes to client side caching, there are several caching manager libraries available for the clients to help in efficiently caching the GET assets and API calls with necessary cache related headers passed by the server APIs.

Example: Setting up Caching in a Flutter Mobile App using Cache Manager

Response Caching Header: Cache-Control

One can ask, “How does the server relay information for the client about how the asset or the data needs to be cached?”. In the world of REST APIs which are communicated over the HTTP protocol, this information is passed down to the client by means of Response Headers.

To be more specific, there are a few headers which would explain how the client should handle this response sent by the server.

The Cache-Control header can contain two kinds of information about the response data; how the client should hold this response and for how much time the data should be persisted.

Cache-Control: <access>,max-age=<duration>

access can be public or private. Public meaning that the client can store this response in a cache if available. Private means that a client can’t store this response in a publicly accessible cache. Instead if the client possesses a private cache, it can be stored and reused from there.

duration is a number in terms of seconds. It means for how much time the client can reuse the response data since its time of creation. Once the max-age is reached, it indicates that the response is now invalid and the client must refresh the cache with a newer response data.

other values a Cache-Control header can be set is no-cache, which means that the client can’t reuse this response for subsequent requests without validation from the server; no-store which means that the client can’t store this response for its use.

How to cache Responses in ASP.NET Core

ASP.NET Core framework provides a Middleware to handle response caching at a single place once the response has been generated and just before it is passed onto the user. Keep in mind that we’re talking about caching only the GET responses and not for any other Request Methods, since other method requests such as POST or PUT or others can contain data which might change frequently or is not suggested to be cached. Hence its a general convention that we cache only GET requests and not any other.

We add the Response Caching Middleware to the services as a Service and then add this Middleware to the pipeline.

namespace MyWebApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
              //add response caching for quicker responses
              services.AddResponseCaching((options) =>
              {
                  // restrict on what max size of response 
                  // can be cached in bytes               
                  options.MaximumBodySize = 1024;
                  options.UseCaseSensitivePaths = true;
              });
         }

        public void Configure(IApplicationBuilder app)
        {	
            app.UseResponseCaching();

            // response caching should be done only for GET requests
            app.Use(async (ctx, next) =>
            {
                if (ctx.Request.Method.Equals(HttpMethod.Get))
                {
                    ctx.Response.GetTypedHeaders().CacheControl =
                    new CacheControlHeaderValue()
                    {
                        Public = true,
                        MaxAge = TimeSpan.FromSeconds(10)
                    };
                    ctx.Response.Headers[HeaderNames.Vary] =
                        new string[] { "Accept-Encoding" };
                }

                await next();
            });
        }
    }
}

In the above code snippet, we can see that there’s one Middleware written on top of the ResponseCaching() middleware already added to the pipeline. Here we specifically set the Cache-Control headers to Public and give a max age of 10 seconds. This means that any GET request served to this server contains a Cache-Control response header of the value

Cache-Control: public,max-age=10

Also we set another Cache related header Vary, which retricts the caching to happen for only requests which contain the same Accept-Encoding as the response content. This is another criteria we add for our client to be able to cache the responses.

How to Cache Asset Files

Asset files such as Style-sheets, images or other static files which have a less frequency of change, do need to be provided with a longer caching in order for the client to request for them less frequently.

Read: Handling Static Files in an ASP.NET Core Application

For this, we induct on the UseStaticFiles() Middleware we use for serving static files from our API, as below:

app.UseStaticFiles(new StaticFileOptions
{
	OnPrepareResponse = ctx =>
        {
            const int durationInSeconds = 60 * 60 * 24 * 365;
            ctx.Context.Response.Headers[HeaderNames.CacheControl] 
			= "public,max-age=" + durationInSeconds;
        }
});

Which will add a Cache-Control for these static files served, of

Cache-Control: public,max-age=31536000  //which is 1 year in seconds

And when the server provides these headers to instruct the client about how the assets are to be cached, the results can be seen like below:

wp-content/uploads/2022/05/caching_response_assets.png

In this way, we can achieve efficient caching of the responses via an ASP.NET Core API, using the built-in response caching Middlewares.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

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