#csharp #dotnet #backend

Day 27: Caching in ASP.NET Core

Welcome to Day 27. What if you have an endpoint that fetches the “Top 100 Selling Books of the Week”? This data doesn’t change every second. If 10,000 users hit this endpoint simultaneously, your database will collapse.

Caching stores the result of an expensive operation in RAM. For the next hour, when users ask for the Top 100 Books, the server just hands them the result from RAM instantly, never touching the database.

IMemoryCache

The simplest form of caching stores the data directly in the web server’s memory.

// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache(); // Register it!
var app = builder.Build();

Now inject IMemoryCache into your endpoint:

using Microsoft.Extensions.Caching.Memory;

app.MapGet("/top-books", async (IMemoryCache cache, AppDbContext db) => 
{
    string cacheKey = "top_books_key";

    // 1. Try to get it from the cache
    if (!cache.TryGetValue(cacheKey, out List<Book> cachedBooks))
    {
        // 2. Not in cache? Do the heavy database work.
        cachedBooks = await db.Books.OrderByDescending(b => b.Sales).Take(100).ToListAsync();

        // 3. Define how long the cache is valid
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromHours(1));

        // 4. Save it to the cache for the next guy!
        cache.Set(cacheKey, cachedBooks, cacheEntryOptions);
    }

    return Results.Ok(cachedBooks);
});

(Note: In .NET 7+, GetOrCreateAsync is a much cleaner, one-line way of doing the above TryGetValue pattern!)

Distributed Caching (Redis)

MemoryCache is great, but what if your API is so busy that you scale it out to 5 different servers? Each server has its own RAM, meaning its own separate cache.

Distributed Caching stores the cached data centrally (usually using Redis), so all 5 servers look at the exact same cache.

ASP.NET Core abstracts this beautifully using IDistributedCache:

// Program.cs
builder.Services.AddStackExchangeRedisCache(options => 
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});

Using it is almost identical to MemoryCache, except it deals purely with byte[] arrays or Strings instead of raw C# objects, meaning you have to JsonSerializer.Serialize your objects before caching them in Redis!

Challenge for Day 27

Enable AddMemoryCache(). Write an endpoint that sleeps for 3 seconds (await Task.Delay(3000)), explicitly returns a string containing the current time (DateTime.Now.ToString()), and caches that string for 30 seconds. Hit the endpoint twice in the browser and watch how fast the second request is!

Tomorrow: Logging & Monitoring.