How to Use Response Caching for Faster Results in ASP.NET Core Minimal API
Introduction
In modern web applications, performance is a key factor for both user satisfaction and system efficiency. One highly effective technique to enhance performance is response caching. With response caching, browsers or other clients store a copy of a server’s response locally. When the same resource is requested again, the cached version is served instantly. This saves time by eliminating unnecessary communication with the server and avoids duplicate work on the server side.
ASP.NET Core provides several powerful caching mechanisms tailored to different scenarios, including in-memory caching, distributed caching, and response caching. In this article, we’ll focus specifically on Response Caching in the context of Minimal APIs. We’ll explore how it works, why it improves performance, and see a step-by-step example of implementing it effectively.
In ASP.NET Core, we have several caching mechanisms available, but today we’ll focus on Response Caching, particularly in the context of Minimal APIs.
What is Response Caching?
Response caching is a way to store the server’s response to a request so that the same data can be reused without requiring the server to execute the same logic repeatedly. This technique benefits both the client and the server by:
- Reducing server load: The server doesn’t need to process the same request multiple times.
- Improving client performance: The client retrieves the data faster since it’s served from the cache.
- Saving bandwidth: Cached responses minimize data transfer, especially for large payloads.
ASP.NET Core’s Response Caching Middleware provides the functionality to cache HTTP responses. However, it works best for idempotent GET requests, where the response doesn’t change unless the resource itself changes.
If you’re interested in further optimizing your caching strategies, check out my previous articles on How to Implement In-Memory Caching in .NET Console App Using C# and How to Implement Redis Cache in .NET using Minimal APIs and C#. Both provide valuable techniques that complement the output caching insights shared here.
Understanding Client-Side vs Server-Side Caching
Before diving into the implementation, let’s clarify the key differences between client-side and server-side caching:
Client-Side Response Caching
- Stores responses in the client’s browser or proxy servers
- Controlled through HTTP headers (Cache-Control, Age, Vary, etc.)
- Reduces network traffic by avoiding unnecessary server requests
- Ideal for static or semi-static data that doesn’t change frequently
- No server memory consumption
Server-Side Caching
- Stores data in server memory or distributed cache (Redis, SQL Server)
- Requires server resources to maintain the cache
- Provides more control over cached data
- Suitable for dynamic data that needs server-side processing
- For more details about server-side caching, check my previous articles on In-Memory and Distributed caching
Brief Explanation of HTTP-Based Response Caching
HTTP-based response caching, defined in RFC 9111, describes how internet caches (such as client-side caches, proxy servers, or reverse proxies) should behave to improve the efficiency of web communication. The Cache-Control HTTP header plays a crucial role in controlling caching behavior, enabling caches to store responses and reuse them to reduce latency, bandwidth usage, and load on servers.
Key Concepts in HTTP Caching
- Cache-Control Directives:
These are instructions included in HTTP headers that guide how requests and responses should be cached. Here are some common directives:public
: The response can be stored and reused by any cache (e.g., browsers, shared proxies).private
: Limits caching to a single client. Shared caches (like proxies) must not store the response.max-age
: Defines how many seconds a response can remain fresh in the cache. Example:max-age=60
: Cache the response for 60 seconds.max-age=2592000
: Cache the response for 30 days (1 month).
no-cache
:- On requests: The cache must revalidate the response with the origin server before serving it.
- On responses: Clients must validate the response with the origin server before reuse.
no-store
: Prohibits the cache from storing any part of the request or response.
- Other Cache Headers:
BeyondCache-Control
, additional headers influence caching:Age
: Indicates the time (in seconds) since the response was generated or validated at the origin server.Expires
: Specifies an expiration date/time after which the response is considered stale.Pragma
: Used for backward compatibility with HTTP/1.0 caches (e.g., settingno-cache
). Ignored ifCache-Control
is present.Vary
: Ensures cached responses are served correctly by requiring certain request headers to match. For example, ifVary: Accept-Language
is set, separate cached responses are stored for each language.
How Does Response Caching Work in ASP.NET Core?
When you enable response caching in ASP.NET Core, the server adds specific HTTP headers to the response. These caching headers inform clients and any intermediate caches (e.g., CDN providers) how to cache the response.
Key headers involved include:
Cache-Control
: Specifies how caching should work (e.g., whether caching is allowed, duration, etc.).Expires
: Indicates when the cached response should expire.Vary
: Modifies how caching is handled for requests with different headers (e.g., caching separate versions of the response based onAccept-Language
).
For example, when a client makes a GET request, a cached response can be served without involving the server if the headers show it is still valid.
Example: Implement Response Caching in ASP.NET Core Minimal API
Disclaimer: This example is intended for educational purposes only. While it demonstrates key concepts, there are more efficient ways to write and optimize this code in real-world applications. Consider this a starting point for learning, and always aim to follow best practices and refine your implementation.
In this example, we’ll demonstrate how to implement Response Caching in an ASP.NET Core Minimal API. You’ll see how to:
- Introduce variations in caching behavior using the
VaryByHeader
. - Configure the
ResponseCache
attribute to cache GET responses for 10 seconds. - Use middleware to inspect cache behavior and provide custom headers.
Prerequisites
Before starting, make sure you have the following installed:
- .NET SDK: Download and install the .NET SDK if you haven’t already.
- Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.
- C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.
Step 1: Create a Minimal API
dotnet new webapi -minimal -n ResponseCachingApi
cd ResponseCachingApi
Add the Microsoft.AspNetCore.ResponseCaching NuGet package:
dotnet add package Microsoft.AspNetCore.ResponseCaching
Step 2: Set Up Output Caching
The first step is to integrate Output Caching Middleware into your ASP.NET Core Minimal API pipeline. This middleware handles caching logic and serves responses directly from the cache when possible, bypassing the endpoint logic entirely.
To enable Output Caching:
- Register the
OutputCache
service within thebuilder.Services.AddOutputCache()
method. - Call
app.UseOutputCache()
in the middleware pipeline to enable the caching system.
Here’s how the setup looks inside the Program.cs
file:
builder.Services.AddOutputCache(); // Register output caching service
...
app.UseOutputCache(); // Enable the middleware
Step 3: Define the Endpoint
Our example demonstrates a /quotes
endpoint that returns a collection of motivational quotes, along with a request counter and the current server time for demonstration purposes. The endpoint is configured to:
- Increment the request counter on every call.
- Enable output caching for 5 seconds, so repeated calls within that duration will reuse the cached response.
Here’s the endpoint definition:
app.MapGet("/quotes", () =>
{
requestCounter++;
var response = new
{
RequestNumber = requestCounter, // Incremented for every request
ServerTime = DateTime.UtcNow, // Tracks current time (cached output will not update during cache duration)
Quotes = quotes, // Array of motivational quotes
CacheInfo = "This response will be cached for 5 seconds"
};
return Results.Ok(response); // Return the response
})
.CacheOutput(options =>
{
options.Expire(TimeSpan.FromSeconds(5)); // Cache response for 5 seconds
options.Tag("User-Agent"); // Ensure caching behavior varies by User-Agent (optional)
})
.WithName("GetQuotes");
Step 4: Enable Output Caching for the Endpoint
The CacheOutput
method is used to enable caching on the specific endpoint. For this example:
- 5-Second Expiry Time: We configure the cache to expire after 5 seconds so that the response is reused for subsequent requests during that period.
- Vary by User-Agent: We use the
Tag("User-Agent")
option, ensuring that separate caches are created for different user agents (like browsers or API tools).
Here’s how you configure it:
.CacheOutput(options => {
// Set cache expiry duration
options.Expire(TimeSpan.FromSeconds(5));
// Vary cache by the User-Agent header
options.Tag("User-Agent");
})
Step 5: Overall Pipeline Setup
Here’s the full setup of your Program.cs
file, including middleware and endpoint configuration:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddOutputCache();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseOutputCache();
app.UseHttpsRedirection();
// In-memory storage for demonstration (not thread-safe, for demo only)
var quotes = new[]
{
"The best way to get started is to quit talking and begin doing.",
"Don't let yesterday take up too much of today.",
"It's not whether you get knocked down, it's whether you get up.",
"The optimist sees opportunity in every difficulty."
};
// Request counter (for demonstration purposes)
int requestCounter = 0;
app.MapGet("/quotes", () =>
{
requestCounter++;
var response = new
{
RequestNumber = requestCounter,
ServerTime = DateTime.UtcNow,
Quotes = quotes,
CacheInfo = "This response will be cached for 5 seconds"
};
return Results.Ok(response);
})
.CacheOutput(options =>
{
options.Expire(TimeSpan.FromSeconds(5));
options.Tag("User-Agent");
})
.WithName("GetQuotes");
app.Run();
Explanation of How Output Caching Works
When you enable output caching using the CacheOutput
method or the [OutputCache]
attribute, the Output Caching Middleware intercepts requests and stores their responses in the cache for a specified duration.
- First Request: When a client requests the
/quotes
endpoint for the first time, the application logic is executed, and the response is generated fresh. The middleware stores this response in the cache.
Example:RequestNumber
:1
ServerTime
:2025-01-10T15:00:00
- Subsequent Requests (Cached): For all requests within 5 seconds of the first request, the middleware serves the cached response without executing the endpoint logic again. Example:
- The
RequestNumber
remains1
(cached value). - The
ServerTime
remains2025-01-10T15:00:00
.
- The
- Cache Expiry: After 5 seconds, the cache expires, and the next request is processed from scratch. The endpoint logic runs and generates a new value for
RequestNumber
andServerTime
.
Implementing Caching in Controller-Based APIs
For a Controller-based API, caching works differently. Response Caching Middleware is used instead of the newer Output Caching Middleware
Feature | Minimal APIs (Output Caching) | Controller-Based APIs (Response Caching) |
---|---|---|
Caching Middleware | Requires OutputCache Middleware | Requires ResponseCaching Middleware |
Caching Setup | .CacheOutput() or [OutputCache] attribute |
[ResponseCache] attribute |
Response Variation | Flexible with .Tag(...) , VaryByQuery , or other custom settings |
Limited to VaryByHeader , no custom variation support |
Best Use Case | New apps with better control of server-side caching behavior | Legacy or controller-based applications |
Final Thoughts
Output Caching in ASP.NET Core is an easy and highly effective way to improve the performance of APIs by caching entire responses on the server side. This dramatically reduces server workload for frequently accessed resources like static data or rarely-changing endpoints.
By following the steps in this guide, you can add fine-tuned output caching to your Minimal APIs, ensuring optimized performance and a faster user experience. You can also build upon this foundation by varying cached responses using headers, query parameters, or other keys, depending on your application’s needs.
For more details on output caching, check out the official ASP.NET Core Documentation.
If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.
Thanks for reading!