ASP.NET Core MVC Mastery

Caching Strategies

1 Views Updated 5/4/2026

Caching Strategies — Scaling for Millions

The fastest database query is the one you never make. Caching temporarily stores the results of expensive operations (complex database joins, external API calls, or HTML rendering) in fast-access memory so subsequent requests execute in milliseconds instead of seconds.

1. Local Memory Cache (IMemoryCache)

Data is stored directly in the physical RAM of the web server executing the request. It is blindingly fast because there is zero network latency or JSON serialization involved.

The Web Farm Problem

If you have 3 load-balanced web servers, and User A logs in on Server 1, their data is cached in Server 1's memory. If their next click routes to Server 2, Server 2 knows nothing about it. Use IMemoryCache strictly for single-instance apps or universally static data (like a list of US States).

// 1. Register in Program.cs
builder.Services.AddMemoryCache();

// 2. Use in Service
public class CatalogService
{
    private readonly IMemoryCache _cache;
    public CatalogService(IMemoryCache cache) => _cache = cache;

    public async Task<List<Category>> GetCategoriesAsync()
    {
        // GetOrCreateAsync elegantly handles cache misses
        return await _cache.GetOrCreateAsync("AllCategories", async entry =>
        {
            // Set expiration TTL to 1 hour
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
            
            // Execute the slow DB query ONLY if "AllCategories" isn't in memory
            return await _db.Categories.ToListAsync();
        });
    }
}

2. Distributed Cache (Redis / IDistributedCache)

For scalable, enterprise environments (Azure, AWS), you must use a Distributed Cache. All web servers talk to a centralized caching server (usually Redis) over the network.

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
// 1. Register in Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "MyApp_";
});

// 2. Use in Service
public class PricingService
{
    private readonly IDistributedCache _cache;
    public PricingService(IDistributedCache cache) => _cache = cache;

    public async Task<ProductPrice> GetPriceAsync(int productId)
    {
        var cacheKey = $"Prc_{productId}";
        
        // Distributed cache ONLY stores bytes/strings. You must serialize manually.
        var cachedString = await _cache.GetStringAsync(cacheKey);
        
        if (cachedString != null)
        {
            return JsonSerializer.Deserialize<ProductPrice>(cachedString);
        }

        // Cache miss: Hit DB
        var price = await _db.GetPriceComplexCalculationAsync(productId);
        
        // Save to cache for next time
        var options = new DistributedCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
            
        await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(price), options);
        
        return price;
    }
}

3. Output Caching (.NET 7+)

Instead of caching raw data, what if you just cache the final, rendered HTML response? .NET 7 introduced Output Caching (replacing the older ResponseCaching).

// Program.cs
builder.Services.AddOutputCache(options =>
{
    // Define global policies
    options.AddPolicy("CacheFor10Secs", policy => policy.Expire(TimeSpan.FromSeconds(10)));
});
app.UseOutputCache(); // Must be placed appropriately in the middleware pipeline
[OutputCache(PolicyName = "CacheFor10Secs")]
public IActionResult HeavyDashboard()
{
    // This action will only execute once every 10 seconds.
    // For all other requests, the framework intercepts the request and instantly returns
    // the cached HTML straight from middleware.
    return View(SlowDatabaseCall());
}

4. Architecture: The Cache Stampede (Dogpiling)

A "Cache Stampede" occurs when a highly searched cache key (e.g., "HomePageData") expires. Suddenly, 5,000 concurrent requests all register a "cache miss" simultaneously, and all 5,000 requests hit the database at the exact same millisecond, instantly crashing the database.

Solution (.NET 9 HybridCache): Modern ASP.NET Core uses locking patterns. The first thread to realize the cache is empty locks the cache key, goes to the database, and forces the other 4,999 threads to wait in line. Once the 1st thread populates the cache, all waiting threads read from the newly refreshed cache, saving the database.

5. Interview Mastery

Q: "When configuring cache expiry, what is the difference between Absolute Expiration and Sliding Expiration?"

Architect Answer: "Absolute Expiration defines a hard deadline—for instance, 10 minutes. Exactly 10 minutes after creation, the item is deleted, regardless of how popular it is. Sliding Expiration resets the timer every time the item is accessed. If set to 10 minutes, and I access it at minute 9, it is granted another 10 minutes of life. A dangerous trap is using *only* Sliding Expiration for rapidly updated items; if it's constantly accessed, it never expires, meaning the data becomes permanently stale. Best practice is to combine them: grant a 10-minute sliding window, but strictly enforce a 1-hour absolute cap."

ASP.NET Core MVC Mastery
1. Core Framework
Introduction to ASP.NET Core MVC
MODULE 1: INTRODUCTION & ENVIRONMENT SETUP
Microsoft Web Stack Overview Evolution of ASP.NET Environment Setup
2. View Engine
Layouts & Partial Views in Razor
MODULE 2: .NET CORE FUNDAMENTALS
Core Concepts Project Structure Startup Flow Middleware Pipeline
MODULE 3: ASP.NET CORE BASICS
Creating Project CLI Commands wwwroot & Static Files
MODULE 4: MVC FUNDAMENTALS
MVC Architecture Dependency Injection (DI) Service Lifetimes
MODULE 5: DATA PASSING TECHNIQUES
ViewData vs ViewBag TempData ViewModel Pattern
MODULE 6: ROUTING
Conventional vs Attribute Routing Custom Constraints
MODULE 7: VIEWS & UI
Razor View Engine Layouts & Sections View Components
MODULE 8: ACTION RESULTS
ViewResult JsonResult RedirectResult
MODULE 9: HTML HELPERS
Form Helpers Custom HTML Helpers
MODULE 10: TAG HELPERS
Built-in Tag Helpers Custom Tag Helpers
MODULE 11: MODEL BINDING
FromQuery vs FromRoute Complex Binding
MODULE 12: VALIDATION
Data Annotations Remote Validation Fluent Validation
MODULE 13: STATE MANAGEMENT
Cookies & Sessions TempData
MODULE 14: FILTERS & SECURITY
Action Filters Authorize Filters Anti-forgery
MODULE 15: ENTITY FRAMEWORK CORE (DEEP DIVE)
DbContext Migrations LINQ Relationships
MODULE 16: DESIGN PATTERNS
Repository Pattern Unit of Work Clean Architecture
MODULE 17: FILE HANDLING
File Upload/Download PDF/Excel Generation
MODULE 18: ADVANCED ASP.NET CORE
Request Lifecycle Bundling & Minification Deployment
MODULE 19: PERFORMANCE & BEST PRACTICES
Caching Strategies Async Programming Secure Coding
MODULE 20: RAZOR PAGES (BONUS)
Razor Pages vs MVC
MODULE 21: REAL-WORLD PROJECTS (🔥 MUST DO)
E-Commerce Web Application Employee Management System