Response Caching in ASP.NET Core

Response Caching in ASP.NET Core

In this article, I will discuss Response Caching (ResponseCacheAttribute) in ASP.NET Core Application with Examples. Please read our previous article discussing Custom Result Filter in ASP.NET Core MVC Application. At the end of this article, you will understand the following pointers:

  • What is Caching?
  • Types of Caching in ASP.NET Core
  • What is Response Caching in ASP.NET Core?
  • How Response Caching Works in ASP.NET Core?
  • Response Caching Examples: Basic, No Caching, Caching with VarByHeader, Location, and VarByQueryKeys.
  • Custom Cache Profiles in ASP.NET Core Response Caching.
  • Benefits of Response Caching in ASP.NET Core?
What is Caching?

Caching is a technique for storing frequently accessed data in a temporary storage area that can be quickly retrieved. This will improve the overall performance of the application by reducing the need to fetch the same data repeatedly from the database or other storage mediums.

Types of Caching in ASP.NET Core: 

ASP.NET Core Supports Several Types of Caching Mechanisms. They are as follows:

  • In-Memory Caching: This is the simplest form of caching, suitable for a single server. It stores data in the Web Server’s Main Memory. It’s fast and suitable for data that doesn’t consume too much memory and doesn’t need to persist beyond the lifetime of the web server process. It is suitable for storing small amounts of data. 
  • Distributed Caching: This is ideal for applications running in multi-server or load-balancing environments where data needs to be shared across multiple servers. It involves storing data in an external system such as Redis, SQL Server, NCache, etc. It is more complex than in-memory caching but necessary for large-scale applications to ensure consistency across sessions and requests.
  • Response Caching: Response Caching refers to the process of storing the output of a request-response cycle in the cache so that future requests for the same resource can be served from the cache instead of regenerating the response from scratch. This technique can significantly improve a web application’s performance, especially for resources that are expensive to generate and don’t change often.

Note: In this session, we will discuss Response Caching. The rest will be discussed in our upcoming sessions.

What is Response Caching?

When a web application receives a request, it often performs several operations, such as database queries, complex calculations, and template rendering, to generate the response. These operations can be time-consuming. If the generated response is the same for each request, it makes sense to store that response once it is generated and then serve the stored response for future requests without regenerating the response again by doing all the processing. This is called Response Caching in ASP.NET Core.

How Response Caching Works in ASP.NET Core?

Please look at the following diagram to understand how response caching works in the ASP.NET Core Application.

How Response Caching Works in ASP.NET Core?

Here’s how response caching works in a web context, particularly in ASP.NET Core Web Application:

Initial Request:
  • A client makes a request to the server for a specific resource.
  • The server processes the request, generates the response, and returns the response to the client who initially made the request.
  • Along with the response, the server includes a Cache-Control HTTP header to indicate that the response can be cached and to specify the caching behavior.
Subsequent Requests:
  • When the client (i.e., browser) needs the same resource again, it first checks the cache.
  • Suppose the cached response is available and fresh (i.e., within the period specified by the Cache-Control header). In that case, the client serves it directly from the cache instead of requesting it from the server again.
What is Proxy Cache in ASP.NET Core?

In the context of ASP.NET Core, Proxy Caches refer to a caching mechanism where a Caching Server (the Proxy Server) is placed between the Client (usually a Web Browser) and the Web Server hosting the ASP.NET Core application. This Proxy Server caches responses from the Web Server, which can then be served quickly to subsequent requests for the same resource. This can significantly improve the performance of web applications by reducing the load on the web server and decreasing response times for end-users.

Server-Side:
  • In ASP.NET Core, you can use the [ResponseCache] attribute to specify caching behavior for controller actions. This attribute allows you to set various parameters, such as duration, location (client, server, or both), and whether the cache should consider query string values.
  • ASP.NET Core includes UseResponseCaching middleware for caching responses on the server. This middleware needs to be configured in the Program.cs file.
Client-Side:
  • Browsers respect the Cache Headers and store the responses in their cache based on their directives, i.e., no-cache, no-store, public, private, etc.
  • Proxy Caches can do the same, helping reduce the load on the server and speeding up response times for users behind the proxy.
How Do We Implement Response Cacheing in ASP.NET Core?

To implement Response Caching, we need to use the ResponseCacheAttribute in ASP.NET Core. This attribute allows developers to control how responses from Web Applications are cached. It can be applied to controller actions or entire controllers and specifies the parameters necessary for setting appropriate HTTP headers for caching responses.

The ResponseCache Attribute itself does not cache content. Instead, it sets the appropriate HTTP headers that control the caching behavior. The ResponseCache Attribute has the following properties:

  • Duration(int): Specifies the time, in seconds, for which the response should be cached. This sets the max-age directive of the Cache-Control header.
  • Location (ResponseCacheLocation): This setting modifies the Cache-Control header and determines the location where the response can be cached. Options include Any, Client, and None.
  • NoStore (bool): When set to true, this property indicates that the response should not be cached (Cache-Control: no-store). It takes precedence over other properties.
  • VaryByHeader (string): Specifies a header name to vary the cached response by (e.g., User-Agent). 
  • VaryByQueryKeys(string): This option allows you to vary the response cache based on the query string parameters. It’s useful for caching different responses for different query string values.
  • CacheProfileName(string): This setting refers to a cache profile defined in the application’s configuration, which allows for centralized cache policy settings.
Examples to Understand Response Caching in ASP.NET Core:

Let’s look at examples to understand how to use the ResponseCacheAttribute in an ASP.NET Core Web API application to implement Response Caching. We will test the functionality using Postman and ASP.NET Core Web API. It will not work as expected if you test the same using Browser. This is because each browser manages the Caching in a different way.

Configure the Response Caching Middleware and Services

Before using the ResponseCacheAttribute, ensure that the response caching middleware is added to the request pipeline in the Program.cs class file. We need to add the AddResponseCaching services and UseResponseCaching middleware components to the request processing pipeline. So, modify the Program.cs class file as follows:

namespace ResponseCachingDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers();

            //Adding Response Caching Service
            builder.Services.AddResponseCaching();

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            //Adding Response Caching Middleware Components
            app.UseResponseCaching();
            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Example to Understand Basic Response Caching

Next, modify the Home Controller as follows. Here, we have applied the Index action method with the [ResponseCache(Duration = 60)] Attribute, which will cache the response for 60 seconds. This means that once a response is generated, it will be reused for subsequent requests for the next 60 seconds.

using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [ResponseCache(Duration = 60)]
        public string Index()
        {
            return $"Response Generated at: {DateTime.Now}";
        }
    }
}

Now, run the application and access the endpoint /api/home/index using Postman, and you will see the following output. If you access the same endpoint within 60 seconds, you will also see the same output.

Example to Understand Basic Response Caching

Further, if you check the Response headers, you will see the Cache-Control header whose value is set to public,max-age=60, as shown in the image below.

What is Caching?

Cache-Control: public, max-age=60: These settings instruct all caches (like Browser, Postman, Fiddler, Swagger, or Proxy Server) to store and reuse the response for 60 seconds without revalidating it. After 60 seconds, the cached response is considered invalid, and a new copy needs to be fetched from the original server.

Example: No Caching

In the following example, we have set the NoStore property of the ResponseCache Attribute to true, which tells the browser not to cache the response.

using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [ResponseCache(Duration = 60, NoStore = true)]
        public string Index()
        {
            return $"Response Generated at: {DateTime.Now}";
        }
    }
}

Now, run the application and access the endpoint /api/home/index using Postman, and you will see the caching is not working. Every time you hit the endpoint, you will get a different response even though we have set the cache duration to 60 seconds. This is because we have set the NoStore Attribute value to true, which means caching is disabled. Now, if you check the Response headers, you will see the Cache-Control header whose value is set to no-store, as shown in the image below.

What is Response Caching in ASP.NET Core?

Cache-Control: no-store: This setting instructs all caches (like Browser, Postman, Fiddler, Swagger, or Proxy Server) not to store any part of the response in the cache. The no-store directive ensures that no copy of the response is saved in any cache. Each time the resource is needed, it must be fetched directly from the server. This guarantees that the information is retrieved in its current state without the risk of serving outdated or potentially compromised data from a cache.

Example to Understand Response Caching with VaryByHeader

In ASP.NET Core, the VaryByHeader Parameter in Response Caching specifies a set of Request Headers (User-Agent, Accept-Language, or any Custom Header) that should trigger the cache to store multiple responses for the same URL based on their values. This is useful when responses vary not only by URL but also by request headers.

When we configure the Response Cache with the VaryByHeader parameter, the caching mechanism considers the URL and the specified header values to determine the cache key. That means different responses are cached and retrieved based on variations in the specified headers. For a better understanding, please modify the Home Controller as follows. Here, we are using the User-Agent header.

using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [ResponseCache(Duration = 60, VaryByHeader = "User-Agent")]
        public string Index()
        {
            return $"Response Generated at: {DateTime.Now}";
        }
    }
}

Now, run the application and access the endpoint /api/home/index using Postman and Swagger. You will get a different response for each client. This is because the User-Agenet header for Swagger and Postman is going to be different. This is also true in the case of Browsers. Each Web Browser has a different User Agent. When the User-Agent is changed, it will fetch the data from the server. Now, if you check the Response header, then you will see along with the Cache-Control header, the Server is also adding the Vary header in the Response, as shown in the below image:

Example to Understand Response Caching with VaryByHeader

Example to Understand Response Caching with Location

By default, Caching is saved on both the client and server sides. Suppose we want to store the cache response only on the client side; then, we need to use the Location property. Using the Location property of the Response Cache Attribute, we can specify where we want to store the response cache using the following ResponseCacheLocation enum.

Example to Understand Response Caching with Location

To store the cache on the browse only, we need to use the Client Option. The ResponseCacheLocation.Client specifies that the response should be cached only on the client side, and in this case, it will set the Cache-Control header to Private. So, modify the Home Controller as follows:

using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client, VaryByHeader = "User-Agent")]
        public string Index()
        {
            return $"Response Generated at: {DateTime.Now}";
        }
    }
}

Now, you can test the functionality using Swagger, and you should see the following output. It will not work on Postman.

How Response Caching Works in ASP.NET Core?

Please check the following Key Points of the behavior of Swagger and Postman:

Swagger Behavior:

When testing APIs, Swagger UI generally respects the HTTP caching headers sent by the server. This means that if your ASP.NET Core application sends a response with Cache-Control set to private, max-age=300, Swagger UI will cache the response as instructed for the duration specified. Subsequent requests to the same endpoint (within the cache duration) might not hit the server if the cached response is deemed valid.

Postman Behavior:

Postman, on the other hand, does not automatically cache responses like a typical web browser would, despite receiving the same caching headers. In Postman, every time you hit the “Send” button, a new request is made to the server, ignoring any previous response cache headers. This is intentional, as Postman is designed for testing and development, where you often need to see live server responses without caching interference.

Cache-Control: Private: The Cache-Control header with the value private is used in Response Caching to indicate that the response is specific to a single user and should not be stored by shared caches, such as proxy servers. It can only be stored in the private cache of the user’s browser.

Example to Understand Response Caching with VaryByQueryKeys

In ASP.NET Core, the VaryByQueryKeys Property in Response Caching is used to specify that the response cache should vary based on the values of specified query string parameters. This is useful when a URL serves different data based on query string parameters. For example, if you have a URL that can return different results based on a query string like ?page=1 or ?page=2, you can use VaryByQueryKeys to cache these responses separately. For a better understanding, please modify the Home Controller as follows:

using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [ResponseCache(Duration = 60, VaryByQueryKeys = new[] { "page" })]
        public string Index(int page)
        {
            return $"Response Generated at: {DateTime.Now}, for Page Number: {page}";
        }
    }
}

In this case, the cached output of the Index action method would differ depending on the value of the page query string parameter. This means if a user requests Index?page=1 and another user requests Index?page=2, they will each get a different cached response specific to the page they requested.

Example to Understand Custom Cache Profile with Response Caching

In ASP.NET Core, Custom Cache Profiles allows us to define Reusable Caching Settings that can be applied across multiple actions or controllers. These profiles are defined in the application’s configuration, typically in the Program.cs class file, and can then be referenced by its name in controllers. Let us modify the Program class as follows. 

using Microsoft.AspNetCore.Mvc;

namespace ResponseCachingDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers(options =>
            {
                //Creating Custom Cache Profiles
                options.CacheProfiles.Add("Default60", new CacheProfile()
                {
                    Duration = 60,
                    Location = ResponseCacheLocation.Any
                });
                options.CacheProfiles.Add("NoCache", new CacheProfile()
                {
                    Location = ResponseCacheLocation.None,
                    NoStore = true
                });
            });

            //Adding Response Caching Service
            builder.Services.AddResponseCaching();

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            //Adding Response Caching Middleware Components
            app.UseResponseCaching();
            app.UseAuthorization();


            app.MapControllers();

            app.Run();
        }
    }
}

Here, we have configured two cache profiles (Default60 and NoCache), as shown in the below image:

Example to Understand Custom Cache Profile with Response Caching

Using Cache Profile in Response Cache Attribute:

Once you define the Cache Profiles, they can be used within controller actions using the CacheProfileName Property of the ResponseCache attribute by referring to the profile name. So, modify the Home Controller as follows. Here, we have specified the Default60 Cache profile within the Index Action method and NoCache with the Privacy action method.

using Microsoft.AspNetCore.Mvc;
namespace ResponseCachingDemo.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [ResponseCache(CacheProfileName = "Default60")]
        public string Index()
        {
            return $"Index Response Generated at: {DateTime.Now}";
        }

        [HttpGet]
        [ResponseCache(CacheProfileName = "NoCache")]
        public string Privacy()
        {
            return $"Privacy Response Generated at: {DateTime.Now}";
        }
    }
}

Now, run the application and access the above endpoints using either Swagger or Postman. You will see that Response Caching works with the Index action method while it is disabled with the Privacy Action method.

Benefits of Response Caching in ASP.NET Core MVC

The following are some of the key benefits of using Response Caching in ASP.NET Core MVC:

  • Improved Performance: By storing the output of actions, Response Caching reduces the time and resources needed to generate responses on subsequent requests. This can lead to faster page load times for users, as the server can serve cached content instead of regenerating it each time.
  • Reduced Server Load: Caching can dramatically reduce the workload on the server. When content is served from the cache, fewer resources are consumed, which means your server can handle more users and requests with the same hardware.
  • Enhanced User Experience: Faster response times generally lead to a better user experience.
  • Customizable Caching Strategies: ASP.NET Core offers a variety of caching strategies, such as caching by duration, parameters, headers, etc. This flexibility allows developers to optimize caching based on the application’s needs and behaviors.

Note: You need to remember that while Response Caching can significantly improve your application’s performance, it may not be suitable for all scenarios. Caching can lead to incorrect or undesirable behavior, such as serving user-specific data that should not be cached or dealing with rapidly changing content.

In the next article, I will discuss the Authorization Filter in ASP.NET Core MVC Application. In this article, I try to explain Response Caching in ASP.NET Core Application with Examples. I hope you enjoy this Response Caching in ASP.NET Core article.

Leave a Reply

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