Caching Strategies

Back

Loading concept...

Caching Strategies in ASP.NET: The Magic Fridge for Your Data! đź§Š

Imagine you have a magic fridge at home. Every time you want a snack, instead of going to the store (which takes forever!), you just open the fridge—and there it is! That’s exactly what caching does for your web app. It saves data close by so you don’t have to fetch it from the slow database every single time.


The Big Picture: Why Do We Need Caching?

Think about this:

  • Going to the database = Walking to the grocery store 🚶‍♂️ (slow!)
  • Using cache = Opening your fridge đź§Š (super fast!)

When thousands of people visit your website, you don’t want each one walking to the store. You want them to just open the fridge!

graph TD A["User Request"] --> B{Is data in cache?} B -->|Yes| C["Return from Cache - FAST!"] B -->|No| D["Fetch from Database"] D --> E["Store in Cache"] E --> C

1. Response Caching: The Browser’s Personal Fridge

What Is It?

Response caching tells the browser to save the response. Next time? Browser uses its own saved copy!

Simple Example

You ask mom for cookies. She says, “I already gave you some—check your lunchbox!” That’s response caching. The browser keeps the cookies.

How It Works in ASP.NET

[ResponseCache(
    Duration = 60
)]
public IActionResult GetProducts()
{
    return View(products);
}

What This Does:

  • Duration = 60 = Save this for 60 seconds
  • Browser remembers and won’t ask again for 1 minute!

The HTTP Headers Behind the Magic

Cache-Control: public, max-age=60

This header is like a sticky note saying: “Good for 60 seconds!”


2. In-Memory Caching: The Server’s Own Fridge

What Is It?

Your server keeps a fridge (memory) right next to it. Super fast because it’s in the same room!

Simple Example

You keep your favorite toy in your pocket. Need it? Already there! No walking to your room.

How It Works in ASP.NET

Step 1: Add the service

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

Step 2: Use it in your controller

public class ProductController
    : Controller
{
    private readonly IMemoryCache _cache;

    public ProductController(
        IMemoryCache cache)
    {
        _cache = cache;
    }

    public IActionResult GetProducts()
    {
        string key = "all_products";

        if (!_cache.TryGetValue(
            key, out List<Product> products))
        {
            // Not in cache? Get from DB
            products = _db.Products.ToList();

            // Put in cache for 5 mins
            _cache.Set(key, products,
                TimeSpan.FromMinutes(5));
        }

        return View(products);
    }
}

When to Use?

  • Single server apps
  • Small to medium data
  • Data that’s okay to lose on restart

3. Output Caching: The Smart Assembly Line

What Is It?

Output caching saves the entire response (the finished product). ASP.NET 7+ has a built-in, powerful version!

Simple Example

A bakery makes 100 identical cakes at once. They don’t bake each one fresh when someone orders—they grab from the ready pile!

How It Works in ASP.NET

Step 1: Add the service

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

app.UseOutputCache();

Step 2: Cache your endpoint

app.MapGet("/products",
    async (ProductDb db) =>
{
    return await db.Products
        .ToListAsync();
})
.CacheOutput(policy =>
    policy.Expire(
        TimeSpan.FromMinutes(10)));

Named Policies (Organize Your Caching!)

builder.Services.AddOutputCache(
    options =>
{
    options.AddPolicy("Products",
        policy =>
            policy.Expire(
                TimeSpan.FromMinutes(10)));

    options.AddPolicy("Categories",
        policy =>
            policy.Expire(
                TimeSpan.FromHours(1)));
});

Then use it:

app.MapGet("/products")
    .CacheOutput("Products");

4. HybridCache: The Best of Both Worlds!

What Is It?

New in .NET 9! It combines in-memory AND distributed caching. Like having a pocket fridge AND a big family fridge!

Simple Example

First, check your pocket (memory cache). Not there? Check the big fridge (distributed cache). Still not there? Go to the store (database).

How It Works

Step 1: Add the service

builder.Services.AddHybridCache();

Step 2: Use it

public class ProductService
{
    private readonly HybridCache _cache;

    public async Task<Product>
        GetProduct(int id)
    {
        return await _cache
            .GetOrCreateAsync(
                quot;product_{id}",
                async cancel =>
                    await _db.Products
                        .FindAsync(id),
                new HybridCacheEntryOptions
                {
                    Expiration =
                        TimeSpan.FromMinutes(5)
                });
    }
}

Why It’s Amazing

  • L1 Cache: In-memory (lightning fast!)
  • L2 Cache: Distributed (shared across servers)
  • Automatically handles both layers!

5. Distributed Caching: The Shared Family Fridge

What Is It?

When you have multiple servers, they all share ONE big fridge. Everyone can see what others stored!

Simple Example

You have 3 brothers. Instead of each having their own small fridge, you share one BIG fridge. If brother #1 puts pizza in, brothers #2 and #3 can grab it too!

graph TD A["Server 1"] --> D["Shared Cache"] B["Server 2"] --> D C["Server 3"] --> D D --> E["All servers see same data!"]

How It Works

public class ProductService
{
    private readonly
        IDistributedCache _cache;

    public async Task<Product>
        GetProduct(int id)
    {
        string key = quot;product_{id}";

        var cached = await _cache
            .GetStringAsync(key);

        if (cached != null)
        {
            return JsonSerializer
                .Deserialize<Product>(
                    cached);
        }

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

        await _cache.SetStringAsync(
            key,
            JsonSerializer
                .Serialize(product),
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromMinutes(10)
            });

        return product;
    }
}

6. Redis Caching: The Super-Powered Shared Fridge

What Is It?

Redis is like a super-fast, super-smart shared fridge that lives on its own machine. It’s the most popular choice for distributed caching!

Simple Example

Imagine a magical fridge that:

  • Never forgets where things are
  • Can find any item in milliseconds
  • All your friends can use from their homes!

How It Works

Step 1: Install the package

dotnet add package
    Microsoft.Extensions.Caching
    .StackExchangeRedis

Step 2: Configure in Program.cs

builder.Services
    .AddStackExchangeRedisCache(
        options =>
{
    options.Configuration =
        "localhost:6379";
    options.InstanceName =
        "MyApp_";
});

Step 3: Use it (same as distributed!)

await _cache.SetStringAsync(
    "user_123",
    jsonData,
    new DistributedCacheEntryOptions
    {
        SlidingExpiration =
            TimeSpan.FromMinutes(20)
    });

Redis Special Powers

  • Pub/Sub: Tell all servers when data changes
  • Data Structures: Lists, sets, sorted sets
  • Persistence: Can survive restarts

7. ETag Handling: The Smart Expiry Date

What Is It?

ETags are like a version number on your data. Browser asks: “Do I have the latest version?” Server says: “Yes!” or “No, here’s the new one.”

Simple Example

Your homework has version “v3” written on top. Teacher asks to see it. You show “v3”. Teacher says, “That’s the latest—no need to print again!”

graph TD A["Browser: I have ETag abc123"] --> B["Server checks"] B -->|Same version| C["304 Not Modified - Use yours!"] B -->|Different| D[200 OK - Here's new data]

How It Works

public IActionResult GetProduct(int id)
{
    var product = _db.Products.Find(id);

    // Create ETag from data
    string etag = quot;\"{product.Version}\"";

    // Check if browser has it
    var requestEtag = Request.Headers
        .IfNoneMatch.FirstOrDefault();

    if (requestEtag == etag)
    {
        // Browser has latest!
        return StatusCode(304);
    }

    // Send new data with ETag
    Response.Headers.ETag = etag;
    return Ok(product);
}

Why It’s Powerful

  • Saves bandwidth (don’t resend same data)
  • Browser still validates (not blindly using old data)
  • Perfect for data that changes occasionally

8. Cache Invalidation: Cleaning the Fridge!

What Is It?

Sometimes data changes. We need to throw out the old and make room for the new! This is the HARDEST part of caching.

Simple Example

You cached that there are 10 cookies. But mom baked 10 more! You need to update your memory: now there are 20!

“There are only two hard things in Computer Science: cache invalidation and naming things.” — Phil Karlton

Strategies for Invalidation

1. Time-Based (Simplest)

_cache.Set("products", products,
    TimeSpan.FromMinutes(5));
// Auto-expires in 5 minutes

2. Manual Invalidation

public async Task UpdateProduct(
    Product product)
{
    await _db.SaveChangesAsync();

    // Remove old cache
    _cache.Remove(quot;product_{product.Id}");
    _cache.Remove("all_products");
}

3. Tag-Based Invalidation (Output Cache)

// Tag cached items
app.MapGet("/products", ...)
    .CacheOutput(p => p.Tag("products"));

// Invalidate by tag
await cache.EvictByTagAsync("products");

4. Pattern-Based (Redis)

// Delete all product keys
var keys = server.Keys(
    pattern: "product_*");

foreach (var key in keys)
{
    await _cache.RemoveAsync(key);
}

The Golden Rules of Invalidation

  1. Invalidate immediately when data changes
  2. Use short TTLs for frequently changing data
  3. Use tags to group related cached items
  4. Be aggressive — when in doubt, invalidate!

Quick Comparison: Which Cache Should I Use?

Scenario Best Choice
Single server, simple app In-Memory Cache
Multiple servers Distributed (Redis)
Static API responses Response Caching
Full page caching Output Caching
Modern .NET 9 app HybridCache
High-traffic, complex Redis

The Complete Journey

graph TD A["Request Arrives"] --> B{Response Cache?} B -->|Hit| C["Return Cached Response"] B -->|Miss| D{In-Memory Cache?} D -->|Hit| E["Return from Memory"] D -->|Miss| F{Distributed Cache?} F -->|Hit| G["Return from Redis"] F -->|Miss| H["Query Database"] H --> I["Store in All Caches"] I --> J["Return Fresh Data"]

Remember This! 🎯

  1. Caching = Speed — Always use appropriate caching
  2. Start Simple — In-memory first, scale to Redis
  3. Set Expiry — Never cache forever
  4. Invalidate Smart — When data changes, update cache
  5. ETags for Validation — Let browsers check for freshness
  6. HybridCache is the Future — Use it in .NET 9+

Your app is now like a well-organized kitchen with fridges everywhere—fast, efficient, and ready to serve! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.