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
Enter fullscreen mode Exit fullscreen mode
If thousands of users request the same product catalog, your application might repeatedly:
HTTP Request
↓
Controller
↓
Database Query
↓
Business Logic
↓
JSON Response
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
Later:
Second request
↓
GET /api/products
↓
Cached response
↓
Return immediately
Enter fullscreen mode Exit fullscreen mode
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();
Enter fullscreen mode Exit fullscreen mode
The important pieces are:
AddOutputCache()
↓
Configure caching
↓
UseOutputCache()
↓
Apply caching policies
Enter fullscreen mode Exit fullscreen mode
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" }
});
}
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
With output caching:
Request 1 → Database → Cache
Request 2 → Cache
Request 3 → Cache
Request 4 → Cache
Request 5 → Cache
Enter fullscreen mode Exit fullscreen mode
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));
});
});
Enter fullscreen mode Exit fullscreen mode
Then apply the policy:
[OutputCache(PolicyName = "ProductsPolicy")]
[HttpGet]
public IActionResult GetProducts()
{
return Ok(products);
}
Enter fullscreen mode Exit fullscreen mode
This makes caching rules easier to manage as your application grows.
5. Cache by Query String
Consider an endpoint:
GET /api/products?category=laptop
Enter fullscreen mode Exit fullscreen mode
and:
GET /api/products?category=mobile
Enter fullscreen mode Exit fullscreen mode
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");
});
});
Enter fullscreen mode Exit fullscreen mode
Now the cache can maintain separate responses for different category values.
Conceptually:
/products?category=laptop
↓
Cache A
/products?category=mobile
↓
Cache B
Enter fullscreen mode Exit fullscreen mode
6. Cache by Header
Sometimes the response depends on a request header.
For example:
Accept-Language
Enter fullscreen mode Exit fullscreen mode
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");
});
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
and the response is:
{
"name": "User A",
"email": "[email protected]"
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
For example:
GET /api/products
Enter fullscreen mode Exit fullscreen mode
might be an excellent caching candidate.
But:
GET /api/my-orders
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
The response is cached.
Then the database changes:
Product price = ₹45,000
Enter fullscreen mode Exit fullscreen mode
If the cache is still valid, users might continue seeing:
₹50,000
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
If this endpoint takes 500 ms because of expensive database work, caching may provide a large benefit.
But if another endpoint takes:
2 ms
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
If every request performs the same database query, the database receives:
10,000 queries/minute
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
For example:
Before caching
Average response: 450 ms
After caching
Average response: 35 ms
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
A sensible starting point could be:
/products
→ Cache for 5 minutes
/categories
→ Cache for 30 minutes
/orders
→ Carefully evaluate
/profile
→ Avoid shared output caching
Enter fullscreen mode Exit fullscreen mode
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."
Enter fullscreen mode Exit fullscreen mode
The goal is:
"Cache the right responses for the right amount of time."
Enter fullscreen mode Exit fullscreen mode
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.