ASP.NET Core Output Caching: How to Make Web APIs Faster in .NET
When an API receives the same request repeatedly, performing the same database query and rebuilding the same response every time can waste valuable resources.
For example, imagine this endpoint:
GET /api/products
If thousands of users request the same product catalog, your application might repeatedly:
HTTP Request
↓
Controller
↓
Database Query
↓
Business Logic
↓
JSON Response
For data that doesn't change frequently, this can create unnecessary database load.
ASP.NET Core provides Output Caching to help solve this problem.
Instead of executing the complete request pipeline every time, the application can temporarily store the generated response and reuse it for subsequent requests.
In this tutorial, we'll look at how Output Caching works, how to configure it, how to invalidate cached responses, and when you should avoid using it.
What Is Output Caching?
Output caching stores the generated response from an endpoint.
For example:
First request
↓
GET /api/products
↓
Execute controller
↓
Query database
↓
Generate response
↓
Store response in cache
Later:
Second request
↓
GET /api/products
↓
Cached response
↓
Return immediately
The database doesn't need to be queried again while the cached response is valid.
Output Caching vs Response Caching
These two concepts are often confused.
Response Caching
Response caching mainly relies on HTTP caching semantics and headers.
Output Caching
Output caching is controlled by ASP.NET Core and allows your application to decide which responses should be cached and for how long.
Output caching provides more control over server-side response caching.
1. Add Output Caching
Start by registering the output-cache services.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOutputCache();
var app = builder.Build();
app.UseOutputCache();
app.MapControllers();
app.Run();
The important pieces are:
AddOutputCache()
↓
Configure caching
↓
UseOutputCache()
↓
Apply caching policies
2. Cache an API Endpoint
You can apply output caching to an endpoint using the OutputCache attribute.
For example:
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet]
[OutputCache(Duration = 60)]
public IActionResult GetProducts()
{
return Ok(new[]
{
new { Id = 1, Name = "Laptop" },
new { Id = 2, Name = "Keyboard" },
new { Id = 3, Name = "Mouse" }
});
}
}
The response can now be cached for 60 seconds.
During that period, subsequent requests can receive the cached response instead of executing the controller again.
3. Why This Can Improve Performance
Without caching:
Request 1 → Database
Request 2 → Database
Request 3 → Database
Request 4 → Database
Request 5 → Database
With output caching:
Request 1 → Database → Cache
Request 2 → Cache
Request 3 → Cache
Request 4 → Cache
Request 5 → Cache
This can significantly reduce repeated work for suitable endpoints.
The biggest benefit is often reduced load on downstream dependencies such as databases and external APIs.
4. Configure a Default Cache Policy
Instead of adding attributes to every endpoint, you can define policies.
For example:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("ProductsPolicy", policy =>
{
policy.Expire(TimeSpan.FromMinutes(5));
});
});
Then apply the policy:
[OutputCache(PolicyName = "ProductsPolicy")]
[HttpGet]
public IActionResult GetProducts()
{
return Ok(products);
}
This makes caching rules easier to manage as your application grows.
5. Cache by Query String
Consider an endpoint:
GET /api/products?category=laptop
and:
GET /api/products?category=mobile
These requests should not necessarily receive the same cached response.
You can configure caching based on query-string values.
For example:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("ProductsPolicy", policy =>
{
policy
.Expire(TimeSpan.FromMinutes(5))
.SetVaryByQuery("category");
});
});
Now the cache can maintain separate responses for different category values.
Conceptually:
/products?category=laptop
↓
Cache A
/products?category=mobile
↓
Cache B
6. Cache by Header
Sometimes the response depends on a request header.
For example:
Accept-Language
If your API returns localized content, caching should take the language into account.
A policy can vary based on request headers.
options.AddPolicy("LocalizedPolicy", policy =>
{
policy
.Expire(TimeSpan.FromMinutes(5))
.SetVaryByHeader("Accept-Language");
});
Now English and Hindi responses, for example, can be cached separately.
7. Be Careful With User-Specific Data
This is one of the most important Output Caching considerations.
Suppose you have:
GET /api/profile
and the response is:
{
"name": "User A",
"email": "usera@example.com"
}
If you cache this incorrectly, another user could potentially receive the wrong cached response.
That's a serious security problem.
Be very careful when caching endpoints containing:
- User profiles
- Account information
- Payments
- Orders
- Private documents
- Authorization-specific information
- Personalized dashboards
Caching should never accidentally share private data between users.
8. Public vs Personalized Endpoints
A useful rule is:
Public data
↓
Usually good candidate for caching
User-specific data
↓
Requires careful cache variation
For example:
GET /api/products
might be an excellent caching candidate.
But:
GET /api/my-orders
requires much more careful consideration.
Before enabling caching, ask:
Can two different users safely receive the same response?
If the answer is no, don't use a simple shared output-cache policy.
9. Cache Expiration
The cache duration should depend on how frequently the data changes.
For example:
Product categories
→ 30 minutes
Product catalog
→ 5 minutes
Frequently changing stock
→ Very short duration
Real-time account balance
→ Usually don't cache this way
There is no universal cache duration.
The correct value depends on your application's consistency requirements.
10. Cache Invalidation
Caching introduces an important question:
What happens when the underlying data changes?
Imagine:
Product price = ₹50,000
The response is cached.
Then the database changes:
Product price = ₹45,000
If the cache is still valid, users might continue seeing:
₹50,000
until the cache expires or is invalidated.
This is why cache invalidation is one of the most important parts of a caching strategy.
11. Evict Cached Responses
When application data changes, you may need to remove related cached responses.
For example, after updating a product:
Update product
↓
Save database changes
↓
Evict relevant cached response
This ensures the next request generates fresh data.
ASP.NET Core provides output-cache APIs that can be used to control cache eviction.
The exact strategy depends on how your endpoints and policies are structured.
12. Cache Only What Actually Benefits From Caching
Caching everything is not a good strategy.
Consider:
GET /api/products
If this endpoint takes 500 ms because of expensive database work, caching may provide a large benefit.
But if another endpoint takes:
2 ms
caching it may add complexity without meaningful performance improvement.
A good caching strategy focuses on endpoints that are:
- Frequently requested
- Relatively expensive
- Safe to cache
- Not changing constantly
13. Output Caching and Database Performance
Consider an API receiving:
10,000 requests/minute
If every request performs the same database query, the database receives:
10,000 queries/minute
If the response can safely be cached for one minute, the application might perform dramatically fewer database queries.
Conceptually:
10,000 API requests
↓
Output Cache
↓
Small number of DB queries
This can reduce:
- Database CPU
- Network traffic
- Query execution
- Application CPU
- API response latency
14. Output Caching and External APIs
Caching isn't only useful for databases.
Suppose your API calls an external service:
Your API
↓
External API
↓
Response
If the same external data is requested repeatedly, output caching can reduce unnecessary calls.
This can be particularly useful when an external API has:
- Rate limits
- Usage costs
- Slow response times
- Network latency
However, make sure cached data is still acceptable for your business requirements.
15. Don't Cache Sensitive Responses
Avoid blindly caching responses containing sensitive information.
Examples include:
Payment information
Authentication responses
Personal information
Security tokens
Private account data
Caching mistakes can become security vulnerabilities.
Before caching an endpoint, understand exactly what data the response contains.
16. Output Caching in a Multi-Instance Application
Consider an application running with multiple instances:
Load Balancer
↓
┌─────┼─────┐
↓ ↓ ↓
API1 API2 API3
Now caching becomes a distributed-system consideration.
If each instance maintains its own cache, you could have:
API1 → Cache A
API2 → Cache B
API3 → Cache C
The cached data may not be identical between instances.
For larger systems, you should understand how your deployment architecture handles cache storage and consistency.
17. Cache-Control Is Not the Same Thing
Don't confuse server-side Output Caching with browser caching.
Browser caching is controlled through HTTP caching headers and client behavior.
Output caching is an application-side mechanism for reusing generated responses.
A complete caching architecture can involve multiple layers:
Browser
↓
CDN
↓
Reverse Proxy
↓
ASP.NET Core Output Cache
↓
Application
↓
Database
Each layer has different responsibilities.
18. Measure Before and After
Don't assume caching automatically makes everything faster.
Measure your application.
Look at:
Response time
Database CPU
Database query count
API throughput
Application CPU
Memory usage
For example:
Before caching
Average response: 450 ms
After caching
Average response: 35 ms
The actual improvement depends on your workload.
19. Common Output Caching Mistakes
Mistake 1: Caching User-Specific Data
This can cause data leakage.
Mistake 2: Cache Duration Is Too Long
Users may receive stale information.
Mistake 3: Cache Duration Is Too Short
The application may receive little benefit from caching.
Mistake 4: Caching Everything
This adds unnecessary complexity.
Mistake 5: Ignoring Cache Invalidation
Data updates may not become visible immediately.
Mistake 6: Not Measuring Performance
Caching should be driven by actual performance data.
A Practical Example
Suppose your application has:
GET /api/products
GET /api/categories
GET /api/orders
GET /api/profile
A sensible starting point could be:
/products
→ Cache for 5 minutes
/categories
→ Cache for 30 minutes
/orders
→ Carefully evaluate
/profile
→ Avoid shared output caching
The exact values should depend on your application's requirements.
Production Checklist
Before enabling Output Caching, ask:
- [ ] Is this endpoint safe to cache?
- [ ] Is the response public or user-specific?
- [ ] How frequently does the data change?
- [ ] What cache duration makes sense?
- [ ] Does the response vary by query string?
- [ ] Does it vary by headers?
- [ ] What happens when the underlying data changes?
- [ ] Do we need cache invalidation?
- [ ] Are multiple application instances involved?
- [ ] Have we measured performance before and after caching?
Final Thoughts
Output caching can be one of the simplest ways to reduce repeated work in an ASP.NET Core application.
But the goal isn't:
"Cache everything."
The goal is:
"Cache the right responses for the right amount of time."
Start with endpoints that are:
- Frequently requested
- Expensive to generate
- Relatively stable
- Safe to share
Then measure the results.
A good caching strategy can reduce database load, improve API response times, and help your application handle more traffic without simply adding more infrastructure.
The most important rule is:
Performance should never come at the cost of correctness or security.
A Small Developer Tool for Everyday Development
While working with APIs and debugging applications, developers frequently need quick utilities for formatting JSON, decoding JWTs, comparing API responses, converting data formats, and handling other small development tasks.
That's why I built ToolBench — a collection of free browser-based developer tools designed for everyday development work.
Explore it here:
It can be a handy companion when you're working with .NET APIs, JSON, authentication tokens, and other common development tasks.
How are you handling caching in your ASP.NET Core applications?
Are you using Output Caching, Redis, a CDN, browser caching, or a combination of these?
Share your approach in the comments.
Top comments (0)