DEV Community

Cover image for Caching in ASP.NET Core
Donia Shaban
Donia Shaban

Posted on

Caching in ASP.NET Core

Caching in ASP.NET Core — A Complete Guide

Caching is the single most impactful performance optimization you can apply to a web app. The idea is simple: instead of recomputing or re-fetching the same data on every request, you store it somewhere fast and serve it from there. ASP.NET Core gives you three distinct caching mechanisms, each operating at a different layer of your app. Let's break each one down.


Why do we actually use caching?

A 100ms delay causes 7% fewer conversions — meaning if your checkout page takes even one tenth of a second longer, you statistically lose customers. Amazon calculated this years ago and it still holds. A 3-second load time loses 40% of users entirely — they leave before the page finishes.

The reason caching is the go-to fix is the bottleneck breakdown the PDF shows: 60% of slowness comes from the database, 25% from slow API calls, and only 15% from memory/other issues. Caching directly attacks the biggest problem — the DB. Instead of hitting SQL Server 1000 times for the same product data, you hit it once and serve the cached result 999 times. That's where the "80–90% DB load reduction" figure comes from.

The Netflix example (70% startup time reduction) is real — they heavily cache user profiles, recommendation lists, and content metadata in Redis so that when you open the app, almost nothing needs a live DB query.

Where do the actual problems come from?

Database (60% of problems) — This is the classic N+1 query problem, missing indexes, fetching entire tables when you need 3 rows, and hitting the DB on every single request for data that barely changes (like a list of product categories). Caching fixes this directly.

APIs (25% of problems) — Calling external services (payment gateways, weather APIs, third-party data) on every request. If you call an exchange rate API on every page load, you're adding 200–500ms of network latency every time. Cache that response for 5 minutes and the latency disappears.

Memory (15% of problems) — This is actually caused by bad caching, not a lack of it. When you cache without expiration policies or cache huge objects carelessly, you put pressure on the GC (Garbage Collector). The server starts spending CPU time collecting memory instead of serving requests. This is why the "set expiration policies" rule matters so much — cache is not free RAM, it's borrowed RAM.

The performance metrics from the PDF are also worth internalizing for your own apps: target average response time under 200ms, keep CPU below 70%, memory below 80%, and HTTP 5xx errors below 0.1%. Those are the numbers you'd put on a production monitoring dashboard.


1. In-Memory Cache (IMemoryCache)

What it is: Data is stored directly in the server process's RAM. It's the fastest cache available — a dictionary lookup with no network round-trip.

Where it lives: Inside your application process. If you restart the server, the cache is gone. If you have multiple servers behind a load balancer, each server has its own isolated cache.

Best for: Single-server apps, frequently read but rarely changed data (e.g., lookup tables, config values, user roles).

in_memory_cache_flow

How to use it:

// Program.cs
builder.Services.AddMemoryCache();

// In your service
public class ProductService(IMemoryCache cache, AppDbContext db)
{
    public async Task<Product?> GetProductAsync(int id)
    {
        if (cache.TryGetValue($"product:{id}", out Product? cached))
            return cached;

        var product = await db.Products.FindAsync(id);

        var options = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(10))
            .SetSlidingExpiration(TimeSpan.FromMinutes(2));

        cache.Set($"product:{id}", product, options);
        return product;
    }
}
Enter fullscreen mode Exit fullscreen mode

Key concepts to know:

  • Absolute expiration — the item is always removed after X time, no matter how much it's accessed.
  • Sliding expiration — the timer resets every time the item is accessed; it's evicted only if nobody touches it for X time.
  • Eviction policies — when RAM gets tight, ASP.NET Core uses LRU (Least Recently Used) to drop items. You can also set CacheItemPriority to protect critical entries.
  • Cache Stampede — if 100 requests arrive simultaneously on a cache miss, they all hit the DB at once. Use GetOrCreateAsync or a SemaphoreSlim lock to handle this safely.

2. Distributed Cache (IDistributedCache)

What it is: Data is stored in an external shared store — Redis being the most common choice — that all your servers can reach. When you scale out to 3 servers, they all read from and write to the same cache.
Where it lives: Outside your app process, usually Redis or SQL Server.

Best for: Multi-server deployments, session data, anything that must be consistent across servers, large-scale apps.

distributed_cache_flow

How to use it (with Redis):

// Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
    options.InstanceName = "MyApp:";
});

// In your service
public class ProductService(IDistributedCache cache, AppDbContext db)
{
    public async Task<Product?> GetProductAsync(int id)
    {
        var key = $"product:{id}";
        var cached = await cache.GetStringAsync(key);

        if (cached is not null)
            return JsonSerializer.Deserialize<Product>(cached);

        var product = await db.Products.FindAsync(id);

        var options = new DistributedCacheEntryOptions()
            .SetAbsoluteExpirationRelativeToNow(TimeSpan.FromMinutes(10));

        await cache.SetStringAsync(key, JsonSerializer.Serialize(product), options);
        return product;
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that unlike IMemoryCache, the distributed cache stores bytes/strings, so you serialize/deserialize yourself. This is the price of network storage — but the payoff is consistency across all your servers.


3. Output Caching & Response Caching

These two are often confused. They both cache the HTTP response, but they work at different layers.

Response Caching — sets HTTP cache headers (Cache-Control, Expires) that tell the client or proxy to cache the response. The server itself doesn't store anything; it just instructs whoever's asking.

Output Caching (ASP.NET Core 7+) — the server caches the full HTTP response and serves it directly on repeat requests, before your controller action even executes. No business logic, no DB query.

output_vs_response_cache_fixed

Output Caching setup:

// Program.cs
builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(b => b.Cache());
    options.AddPolicy("Products", b => b.Cache()
        .Expire(TimeSpan.FromMinutes(5))
        .Tag("products-tag"));
});

app.UseOutputCache();

// In your controller
[HttpGet("products")]
[OutputCache(PolicyName = "Products")]
public async Task<IActionResult> GetProducts() { ... }

// Invalidation: purge a specific tag
await _cache.EvictByTagAsync("products-tag", token);
Enter fullscreen mode Exit fullscreen mode

Response Caching setup:

// Program.cs
builder.Services.AddResponseCaching();
app.UseResponseCaching();

// In your controller
[HttpGet("products")]
[ResponseCache(Duration = 300, VaryByQueryKeys = new[] { "category" })]
public async Task<IActionResult> GetProducts(string category) { ... }
Enter fullscreen mode Exit fullscreen mode

The ResponseCache attribute tells the framework to emit Cache-Control: public, max-age=300 headers. The browser or CDN does the actual caching.


Quick Comparison

Feature In-Memory Distributed Output Cache Response Cache
Where stored Server RAM Redis / SQL Server Server (middleware) Client / Proxy
Survives restart? No Yes No (unless backed by Redis) Yes (in browser)
Multi-server safe? No Yes Yes N/A
Caches Any object Bytes / strings Full HTTP response Full HTTP response
Granularity Fine (per key) Fine (per key) Per endpoint/route Per URL
Best for Fast lookups, small data Sessions, scaled apps Read-heavy API endpoints Public, static-ish content

Top comments (0)