Server-side, client-side, and proxy caching in one beginner-friendly sample β 7 practical Minimal API endpoints covering every caching scenario with Swagger/OpenAPI.
If this sample saved you time, consider joining our Patreon community. You'll get exclusive .NET tutorials, premium code samples, and early access to new content β all for the price of a coffee.
π Join CodingDroplets on Patreon
Prefer a one-time tip? Buy us a coffee β
- How to configure Response Caching Middleware in ASP.NET Core
- How to cache responses on the server, client, and proxy with
Cache-Control - How to use Vary by query key for query-parameter-dependent caching
- How to use Vary by header for header-based cache variations
- How to disable caching for sensitive or dynamic endpoints
- How to inspect cache behaviour using
Cache-Control,Vary, andAgeresponse headers
Client Request
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Request β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Response Caching Middleware β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Cached response exists? β β
β β YES β return cached response (Age header added) β β
β β NO β forward to endpoint β cache the response β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Minimal API Endpoints β
β Products / User Profile / Time / Random / Private / Login β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTP Response + Cache Headers β
β Cache-Control Β· Vary Β· Age Β· ETag β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Endpoint | Method | Cache Strategy | Duration |
|---|---|---|---|
/api/products |
GET | Server + public | 60 seconds |
/api/products/by-category |
GET | Vary by category query |
120 seconds |
/api/user-profile |
GET | Client-only | 300 seconds |
/api/time |
GET | Vary by Accept-Language header |
10 seconds |
/api/random-number |
GET | No cache (no-store) |
β |
/api/private-data |
GET | No cache (private) | β |
/api/login |
POST | No cache (security) | β |
/api/cache-status |
GET | No cache (diagnostic) | β |
dotnet-response-caching/
βββ DotNetResponseCaching.sln
βββ src/
βββ DotNetResponseCaching.Api/
βββ Program.cs # Middleware setup, endpoints, cache profiles
βββ appsettings.json
βββ Properties/
βββ launchSettings.json
- .NET 10 SDK
- Any IDE: Visual Studio 2022+, VS Code, or JetBrains Rider
# Clone the repo
git clone https://github.com/codingdroplets/dotnet-response-caching.git
cd dotnet-response-caching
# Run the API
dotnet run --project src/DotNetResponseCaching.Api
# Open Swagger UI β http://localhost:5000/swaggerbuilder.Services.AddResponseCaching();
builder.Services.AddControllers(options =>
{
options.CacheProfiles.Add("Default",
new CacheProfile { Duration = 60 }); // 60s, any location
options.CacheProfiles.Add("Short",
new CacheProfile { Duration = 30, Location = ResponseCacheLocation.Client });
options.CacheProfiles.Add("Long",
new CacheProfile { Duration = 300, VaryByQueryKeys = new[] { "category", "page" } });
});
// Must be placed before routing middleware
app.UseResponseCaching();// Server-side cache for 60 seconds
app.MapGet("/api/products", GetProducts)
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(60)));
// Vary by query key β each category cached separately
app.MapGet("/api/products/by-category", GetByCategory)
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(120)).SetVaryByQuery("category"));
// Client-only β no server-side store
app.MapGet("/api/user-profile", GetUserProfile)
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(300)).NoStore());
// No caching for sensitive/dynamic endpoints
app.MapPost("/api/login", Login)
.CacheOutput(p => p.NoStore());Make a request, then check response headers:
curl -I http://localhost:5000/api/products
# Cache-Control: public, max-age=60
# Age: 0 β first hit (not yet cached)
curl -I http://localhost:5000/api/products
# Age: 4 β served from cache| Header | Description |
|---|---|
Cache-Control |
Directives: public, private, no-store, max-age |
Vary |
Which request headers affect cache key (Accept-Language, etc.) |
Age |
Seconds this response has been in cache |
ETag |
Identifier for a specific version of the content |
| Type | What It Caches | Storage | Use When |
|---|---|---|---|
| Response Caching | HTTP responses (this sample) | Server memory / proxy | Public, rarely-changing API responses |
| Output Caching | Entire rendered output | In-memory (server) | Razor Pages, full page caching |
| In-Memory Caching | Arbitrary objects (IMemoryCache) |
Server memory | Internal data, computed results |
| Distributed Caching | Arbitrary objects (Redis / SQL) | External store | Multi-server / load-balanced apps |
- Response caching in ASP.NET Core β Microsoft Learn
- Cache-Control directives β MDN Web Docs
- HTTP Caching β RFC 9111
This project is licensed under the MIT License.
| Platform | Link |
|---|---|
| π Website | https://codingdroplets.com/ |
| πΊ YouTube | https://www.youtube.com/@CodingDroplets |
| π Patreon | https://www.patreon.com/CodingDroplets |
| β Buy Me a Coffee | https://buymeacoffee.com/codingdroplets |
| π» GitHub | http://github.com/codingdroplets/ |
Want more samples like this? Support us on Patreon or buy us a coffee β β every bit helps keep the content coming!