Case 16: Caching at the Edge - Response Caching
The Story
Imagine you have a public API endpoint, /api/news/latest, which fetches the 10 most recent news articles. This data is the same for all users and only needs to be updated every 2 minutes.
Currently, even if the underlying service uses IMemoryCache to get the data quickly, every single request to this endpoint still has to go through the entire ASP.NET Core pipeline (routing, creating the controller, running the action method, and serializing the result to JSON).
The Problem Code (No Response Caching)
[ApiController]
[Route("api/news")]
public class NewsController : ControllerBase
{
private readonly INewsService _newsService;
public NewsController(INewsService newsService)
{
_newsService = newsService;
}
[HttpGet("latest")]
public async Task<IActionResult> GetLatestNews()
{
// Even if the service is cached, this code runs for EVERY request
var latestNews = await _newsService.GetLatestNewsAsync();
return Ok(latestNews);
}
}Why is this a problem?
- Wasted Server Work: Even if
_newsService.GetLatestNewsAsync()returns data fromIMemoryCachein just 1 millisecond, your server still has to spend CPU resources to:- Process the HTTP request.
- Run the routing logic.
- Create a new
NewsControllerinstance. - Call the
GetLatestNewsmethod. - Convert (serialize) the C#
latestNewsobject into a JSON string.
- The Core Issue: The application is repeating the entire request-handling process over and over again just to produce the exact same result for everyone.
The Solution: Use Response Caching Middleware
Response Caching works at the HTTP level. The middleware intercepts an incoming request. If it finds a valid cached response for that URL, it sends the cached response back immediately without running the rest of the pipeline. The request never even reaches the controller.
Here is the better code:
- Register it in
Program.cs:csharpbuilder.Services.AddResponseCaching(); // ... var app = builder.Build(); // ... app.UseResponseCaching(); // Add the middleware to the pipeline app.UseRouting(); // ... - Add an Attribute to the Action:csharp
[HttpGet("latest")] // Instructs the middleware to cache the response of this action for 120 seconds (2 minutes) [ResponseCache(Duration = 120, Location = ResponseCacheLocation.Any)] public async Task<IActionResult> GetLatestNews() { // This logic now only runs for the first request every 2 minutes var latestNews = await _newsService.GetLatestNewsAsync(); return Ok(latestNews); }
What We Gained
- The First Request: The request goes through the pipeline as normal, and the controller action is executed. Before sending the response to the client, the
ResponseCachingMiddlewaresaves a copy of it in memory. - Subsequent Requests (within 2 minutes): The
ResponseCachingMiddlewareintercepts the request, finds the cached copy, and sends it back immediately. The Controller and Service are never executed. - Performance:
- Maximum Server Load Reduction: The server uses almost no CPU for cached requests.
- Ultra-Fast Responses: Response time is minimal because it's just a memory lookup.
- Leverages Client/Proxy Caching:
Location = ResponseCacheLocation.Anyallows the user's browser and intermediate proxies to also cache the response, meaning later requests might not even need to travel to your server at all.
Comparing IMemoryCache and Response Caching
| Criteria | IMemoryCache (Data Caching) | Response Caching (Output Caching) |
|---|---|---|
| Purpose | Caches data inside the application | Caches the entire HTTP response |
| What is Cached | C# objects | JSON, HTML (as bytes) |
| Location | Server-side only | Server, Proxy, Client (browser) |
| Best For | Data that is reused in many places | Public endpoints with the same result for everyone |
Conclusion
- Response Caching is a high-level caching strategy that is extremely effective for public endpoints with data that changes infrequently and is the same for all users.
- It does not replace
IMemoryCachebut complements it. You can use both to create a powerful, multi-layered caching strategy.