DEV Community

Cover image for ASP.NET Core Output Caching: How to Make Web APIs Faster in .NET
ToolBench
ToolBench

Posted on

ASP.NET Core Output Caching: How to Make Web APIs Faster in .NET

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": "usera@example.com"
}
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:

https://toolbenchapp.com/

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)