Response Caching and Output Caching

Tohid haghighi
8 min readJan 13, 2024

Response caching means storing response output. Browsers and other clients use response caching to cache a server’s response in order to retrieve it quickly and efficiently in response to subsequent requests. In ASP.NET Core, response caching reduces server load and improves user experience in web applications. The purpose of this blog is to provide a detailed explanation of response caching in ASP.NET Core.

Output caching is an useful technique that can significantly improve the performance of your web applications. In this blog post, we will explore what output caching is, how it works in ASP.NET Core, and how to implement it in your projects.

Response Caching and Output Caching

What is Response Caching?

Using the response cache, the server can store responses in memory or on disk so that they can be retrieved quickly for subsequent requests. Caching mechanisms check the cache for responses whenever a request is made to the server. Rather than generating a new response, the cache returns the response. Using response caching reduces some server workload and reduces some requests to the server.

Response Caching Headers

The ‘Client and Server’ exchange HTTP header information to cache the response. HTTP caching directives and how we can control caching behavior using them. Cache control specifies how the response can be cached. It is the responsibility of browsers, clients, and proxy servers to honor the cache-control header when it is present in the response.

Main Response Caching Headers are like below,

  • Cache-Control
  • Pragma
  • Vary
  • Cache-Control Header

The Cache-Control header is the main header type for response caching. To add a Cache-Control header in ASP.Net Core, you can use the Response object in your controller’s action method. So, Let’s Start with the common cache-control directives:

  1. public: this cache can store the response either on the client side or at a shared location.
  2. private: This Private Cache always stores Client Side Response But does Not Shred the Cache From The Client Side.
  3. max-age: this cache-control header represents a time to hold a response in the cache.
  4. no-cache: this value indicates that the client should not cache the response.
  5. no-store: this cache must not store the response.

Public

​public class HomeController: Controller
{
[HttpGet]
[ResponseCache(Duration = 180, Location = ResponseCacheLocation.Any)]
public IActionResult getCache()
{
return Ok($"Responses are generated on {DateTime.Now}");
}
}

This Duration property will generate the max-age header, which we use to define the duration of the cache for 3 minutes (180 seconds). The Location property will define the Location within the cache-control header.

So, the API endpoint and verify these response headers:

cache-control: public,max-age=180

​The status code indicates that the response comes from the disk cache:

Status Code: 200

Private

​public class HomeController: Controller
{
[HttpGet]
[ResponseCache(Duration = 180, Location = ResponseCacheLocation.Client)]
public IActionResult getCache()
{
return Ok($"Responses are generated on {DateTime.Now}");
}
}

This changes the value of the cache control header to private, which means that only the client can cache the response:

cache-control: private,max-age=180

No-Cache

Now let us update the Location parameter to ResponseCacheLocation. None:

public class HomeController: Controller
{
[HttpGet]
[ResponseCache(Duration = 180, Location = ResponseCacheLocation.None)]
public IActionResult getCache()
{
return Ok($"Responses are generated on {DateTime.Now}");
}
}

Because the cache-control and pragma headers are set to no-cache, the client is unable to use a cached response without first verifying it with the server:

cache-control: no-cache,max-age=180

pragma: no-cache

The server generates a new response each time, and the browser does not use the cached response.

NoStore

Gets or sets the value to determine whether to store the data. If NoStore is decorated with the value “true”, the “cache-control” header is set to “no-store”. It ignores the “Location” and the parameter has ignored the values; otherwise values “None”.

​public class HomeController: Controller
{
[HttpGet]
[ResponseCache(Duration = 180, Location = ResponseCacheLocation.Any,NoStore =True)]
public IActionResult getCache()
{
return Ok($"Responses are generated on {DateTime.Now}");
}
}

This sets the response header cache control to no-store. This means that the client should not cache the response:

cache-control: no-store

VaryByHeader

Sets or gets the “Vary” response header value. ResponseCache’s VaryByHeader property allows us to set the vary header:

Now that User-Agent is the value for the VaryByHeader property, the cached response will be used as long as the request originates from the same client device. Once the User-Agent value on the client device changes, a new response will be fetched from the server. Let’s verify this.

​public class HomeController: Controller
{
[HttpGet]
[ResponseCache(Duration = 180, Location = ResponseCacheLocation.Any,VaryByHeader="User-Agent")]
public IActionResult getCache()
{
return Ok($"Responses are generated on {DateTime.Now}");
}
}

In the response headers, check for the Vary header:

vary: User-Agent

The application is run in desktop mode, then you can see the response header “Vary” contains the value “User-Agent” (see below).

  • user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
  • user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1

VaryByQueryKeys Property

The VaryByQueryKeys property can be used to trigger the server to deliver a fresh response every time the response will change. There is a change in query string parameters.

​public class HomeController: Controller
{
[HttpGet]
[ResponseCache(Duration = 180, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new string[] { "Id " })]
public IActionResult getCache()
{
return Ok($"Responses are generated on {DateTime.Now}");
}
}

For example, When the Id value change and then URI also change, and we want to generate a new response:

/api/Home?Id=1

/api/Home?Id=2

Cache Profile

In this project, you can use Response Cache attributes, as most action methods have the same input parameters. With ASP.Net Core, all parameter options are related in a Program class and with it’s name and which can be used in the Response Cache attribute to remove duplicate parameter settings.

Cache3 is a new cache profile that has a time duration of 3 minutes and a location of the public.

builder.Services.AddControllers (option =>
{
option.Cache Profiles.Add("Cache3",
new CacheProfile()
{
Duration= 180,
Location = ResponseCacheLocation.Any
});
});
public class HomeController: Controller
{
[HttpGet]
[ResponseCache (Cache ProfileName ="Cache3")]
public IActionResult getCache()
{
return Ok ($"Responses are generated on (DateTime.Now}");
}
}

The defined cache-control response (see below):

cache-control: public,max-age=180

What is Output Caching?

Output caching is the process of storing the response of a web request in memory or on disk and reusing it for subsequent requests that match certain criteria. The purpose of output caching is to reduce the amount of work that the web server has to do for each request, such as executing code, querying databases, or rendering views. By serving cached responses, the web server can handle more requests faster and with less resources.

There are different types of output caching, depending on where the response is stored and who can access it. The most common types are:

  • Client-side caching: The response is stored in the browser’s cache and reused by the same client. This type of caching is controlled by HTTP headers such as Cache-Control and Expires, which specifies how long and under what conditions the browser can use the cached response.
  • Proxy caching: The response is stored in an intermediate proxy server (such as a CDN) and reused by multiple clients. This type of caching is also controlled by HTTP headers, such as ETag, which specifies how the proxy can validate and vary the cached response.
  • Server-side caching: The response is stored in the web server’s memory or disk and reused by multiple clients. This type of caching is controlled by the web application’s logic, which decides what responses to cache and how to invalidate them.

Output Caching in ASP.NET Core

ASP.NET Core is a modern web framework that supports output caching out of the box. Output Caching Middleware, which is available in ASP.NET Core 7.0 and later, caches responses based on configuration rather than HTTP headers. It works like a server-side cache and can benefit UI apps as well as API requests. With output caching, you can specify what responses to cache, how long to cache them, where to store them, and how to invalidate them. Output caching also supports cache entry dependencies, storage medium extensibility, cache profiles, and more.

Implementing Output Caching in ASP.NET Core

To implement output caching in your ASP.NET Core app, you need to follow these steps:

Configuring output caching in Program.cs

You need to add the output caching services to your dependency injection container by calling AddOutputCache in your ConfigureServices method. You can also define global or named cache policies that specify the default or custom settings for output caching. For example:

builder.Services.AddOutputCache(options =>
{
// Add a base policy that applies to all endpoints
options.AddBasePolicy(basePolicy => basePolicy.Expire(TimeSpan.FromSeconds(120)));

// Add a named policy that applies to selected endpoints
options.AddPolicy("Expire20", policyBuilder => policyBuilder.Expire(TimeSpan.FromSeconds(20)));
});

and then add the below code to use output caching.

app.UseOutputCache();

Using attributes for action-level output caching

You need to apply the [OutputCache] attribute to your endpoints (such as actions or pages) that you want to enable output caching for. You can use the attribute without parameters to apply the base policy or with a policy name or inline settings to apply a custom policy. For example.

// Use the base policy
[OutputCache]
public IActionResult Index()
{
return View();
}

// Use a named policy
[OutputCache("Expire20")]
public IActionResult About()
{
return View();
}

// Use inline settings
[OutputCache(Duration = 30)]
public IActionResult Contact()
{
return View();
}

Conclusion

ResponseCache

First, the ResponseCache can be divided in 2 parts, that work independently and are different concepts of how and where the information would be cached. Let´s catch them up:

ResponseCacheAttribute: Basically it manipulates cache header like Vary, Cache-Control and others. It works telling the browsers or proxies to store (or not) the response content. This technique can reduce the number of requests done to the server, if used correctly.

The ResponseCache attribute sets response caching headers. Clients and intermediate proxies should honor the headers for caching responses. under the HTTP 1.1 Caching specification

Response Caching Middleware: Basically, it is used to make server-side caching based on headers defined by ResponseCacheAttribute. Depending on the Request Headers sent to the server, the response would never be cached on server side.

Enables caching server responses based on HTTP cache headers. Implements the standard HTTP caching semantics. Caches based on HTTP cache headers like proxies do.

Is typically not beneficial for UI apps such as Razor Pages because browsers generally set request headers that prevent caching. Output caching, which is available in ASP.NET Core 7.0 and later, benefits UI apps. With output caching, configuration decides what should be cached independently of HTTP headers.

And at this point that OutputCache comes as a replacement for Response Caching Middleware.

OutputCache (available in ASP.NET Core 7.0 and later)

The OutputCache configuration decides what should be cached (server side) independently of HTTP headers. Also, it comes with a lot of new features like cache entry invalidation, storage medium extensibility and others.

To take the benefits from both worlds you can use:

  • ResponseCacheAttribute: To manipulate response headers and enable the clients/proxies to store content on client side;
  • OutputCache: To store responses on server side and increase throuthput when responses are cached.

--

--

Tohid haghighi

Full-Stack Developer | C# | .NET Core | Vuejs | TDD | Javascript