ASP.NET Core  

ASP.NET Core Caching Mastery: Redis, Memory Cache, Distributed Patterns & Performance Optimization (Part - 27 of 40)

caching

Previous article:   ASP.NET Core Performance Hacks: Async, Profiling & Optimization Techniques (Part - 26 of 40)

Table of Contents

  1. The Caching Revolution

  2. Caching Fundamentals & Architecture

  3. In-Memory Caching Deep Dive

  4. Distributed Caching with Redis

  5. Response Caching Strategies

  6. Real-World E-Commerce Caching

  7. Cache Invalidation Patterns

  8. Performance Monitoring & Analytics

  9. Advanced Caching Patterns

  10. Security & Best Practices

  11. Testing Caching Strategies

  12. Production Deployment

1. The Caching Revolution

Why Caching is Your Performance Silver Bullet

Caching transforms application performance by reducing database load, decreasing response times, and improving scalability. In modern web applications, caching isn't just an optimization—it's a necessity.

Real-Life Analogy: Imagine a busy coffee shop. Without caching, every customer order would require:

  • Going to the supplier warehouse (database)

  • Selecting beans (query execution)

  • Grinding fresh (data processing)

  • Brewing individually (response generation)

With caching, popular drinks are pre-prepared and ready to serve instantly!

  
    // Performance impact demonstrationpublic class ProductServiceWithoutCaching{
    private readonly ApplicationDbContext _context;
    
    public async Task<Product> GetProductAsync(int id)
    {
        // Every call hits the database - SLOW!
        return await _context.Products
            .Include(p => p.Category)
            .Include(p => p.Reviews)
            .FirstOrDefaultAsync(p => p.Id == id);
    }}

public class ProductServiceWithCaching{
    private readonly ApplicationDbContext _context;
    private readonly IMemoryCache _cache;
    
    public async Task<Product> GetProductAsync(int id)
    {
        // Try cache first - FAST!
        if (_cache.TryGetValue($"product_{id}", out Product product))
            return product;
            
        // Cache miss - get from database
        product = await _context.Products
            .Include(p => p.Category)
            .Include(p => p.Reviews)
            .FirstOrDefaultAsync(p => p.Id == id);
            
        // Store in cache for future requests
        _cache.Set($"product_{id}", product, TimeSpan.FromMinutes(30));
        
        return product;
    }}
  

The Caching Performance Impact

ScenarioWithout CachingWith CachingImprovement
Database Queries1000 queries/sec50 queries/sec20x reduction
Response Time200ms5ms40x faster
Server Load80% CPU20% CPU4x reduction
Cost$1000/month$250/month75% savings

2. Caching Fundamentals & Architecture

Caching Architecture Patterns

  
    // Comprehensive caching service interfacepublic interface ICacheService{
    Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
    Task<T> GetAsync<T>(string key);
    Task SetAsync<T>(string key, T value, TimeSpan? expiration = null);
    Task RemoveAsync(string key);
    Task<bool> ExistsAsync(string key);
    Task RemoveByPatternAsync(string pattern);}

// Implementation supporting multiple cache providerspublic class CacheService : ICacheService{
    private readonly IMemoryCache _memoryCache;
    private readonly IDistributedCache _distributedCache;
    private readonly ILogger<CacheService> _logger;
    private readonly CacheSettings _settings;
    
    public CacheService(IMemoryCache memoryCache, IDistributedCache distributedCache, 
                       ILogger<CacheService> logger, IOptions<CacheSettings> settings)
    {
        _memoryCache = memoryCache;
        _distributedCache = distributedCache;
        _logger = logger;
        _settings = settings.Value;
    }
    
    public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
    {
        try
        {
            // Try memory cache first (fastest)
            if (_memoryCache.TryGetValue(key, out T cachedValue))
            {
                _logger.LogDebug("Memory cache hit for key: {Key}", key);
                return cachedValue;
            }
            
            // Try distributed cache
            var distributedValue = await _distributedCache.GetAsync<T>(key);
            if (distributedValue != null)
            {
                _logger.LogDebug("Distributed cache hit for key: {Key}", key);
                
                // Populate memory cache for faster subsequent access
                var memoryCacheOptions = new MemoryCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = expiration ?? _settings.DefaultExpiration
                };
                _memoryCache.Set(key, distributedValue, memoryCacheOptions);
                
                return distributedValue;
            }
            
            // Cache miss - execute factory method
            _logger.LogDebug("Cache miss for key: {Key}, executing factory", key);
            var value = await factory();
            
            if (value != null)
            {
                // Store in both caches
                var cacheExpiration = expiration ?? _settings.DefaultExpiration;
                
                // Memory cache
                var memoryOptions = new MemoryCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = cacheExpiration
                };
                _memoryCache.Set(key, value, memoryOptions);
                
                // Distributed cache
                var distributedOptions = new DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = cacheExpiration
                };
                await _distributedCache.SetAsync(key, value, distributedOptions);
            }
            
            return value;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in GetOrCreateAsync for key: {Key}", key);
            
            // If caching fails, fall back to factory method
            return await factory();
        }
    }
    
    public async Task<T> GetAsync<T>(string key)
    {
        // Implementation similar to above without factory fallback
        if (_memoryCache.TryGetValue(key, out T memoryValue))
            return memoryValue;
            
        var distributedValue = await _distributedCache.GetAsync<T>(key);
        if (distributedValue != null)
        {
            // Populate memory cache
            _memoryCache.Set(key, distributedValue, 
                TimeSpan.FromMinutes(_settings.MemoryCacheExpirationMinutes));
            return distributedValue;
        }
        
        return default(T);
    }
    
    public async Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
    {
        var cacheExpiration = expiration ?? _settings.DefaultExpiration;
        
        // Memory cache
        _memoryCache.Set(key, value, cacheExpiration);
        
        // Distributed cache
        await _distributedCache.SetAsync(key, value, 
            new DistributedCacheEntryOptions 
            { 
                AbsoluteExpirationRelativeToNow = cacheExpiration 
            });
    }
    
    public async Task RemoveAsync(string key)
    {
        _memoryCache.Remove(key);
        await _distributedCache.RemoveAsync(key);
        _logger.LogInformation("Cache removed for key: {Key}", key);
    }
    
    public async Task<bool> ExistsAsync(string key)
    {
        return _memoryCache.TryGetValue(key, out _) || 
               await _distributedCache.GetAsync(key) != null;
    }
    
    // Pattern-based removal for cache invalidation
    public async Task RemoveByPatternAsync(string pattern)
    {
        // This is a simplified version - actual implementation depends on cache provider
        _logger.LogInformation("Removing cache entries matching pattern: {Pattern}", pattern);
        
        // In real implementation, you'd use Redis keys command or similar
        // This is a conceptual implementation
    }}

// Configuration modelpublic class CacheSettings{
    public TimeSpan DefaultExpiration { get; set; } = TimeSpan.FromMinutes(30);
    public int MemoryCacheExpirationMinutes { get; set; } = 10;
    public string RedisConnectionString { get; set; }
    public bool UseDistributedCache { get; set; } = true;}
  

Cache Configuration in Program.cs

  
    // Program.cs - Comprehensive caching setupvar builder = WebApplication.CreateBuilder(args);

// Configure cache settings
builder.Services.Configure<CacheSettings>(builder.Configuration.GetSection("CacheSettings"));

// Add memory cache (always available)
builder.Services.AddMemoryCache(options =>{
    options.SizeLimit = 1024 * 1024 * 100; // 100MB limit
    options.CompactionPercentage = 0.25; // Compact when 25% full});

// Add distributed cache based on configurationvar cacheSettings = builder.Configuration.GetSection("CacheSettings").Get<CacheSettings>();if (cacheSettings.UseDistributedCache && !string.IsNullOrEmpty(cacheSettings.RedisConnectionString)){
    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = cacheSettings.RedisConnectionString;
        options.InstanceName = "MyApp:";
    });
    
    // Register Redis connection for direct access
    builder.Services.AddSingleton<IConnectionMultiplexer>(sp => 
        ConnectionMultiplexer.Connect(cacheSettings.RedisConnectionString));}else{
    // Fallback to distributed memory cache (for development)
    builder.Services.AddDistributedMemoryCache();}

// Register caching services
builder.Services.AddScoped<ICacheService, CacheService>();
builder.Services.AddScoped<IProductCacheService, ProductCacheService>();
builder.Services.AddScoped<IUserCacheService, UserCacheService>();

// Add response caching
builder.Services.AddResponseCaching(options =>{
    options.MaximumBodySize = 1024 * 1024; // 1MB
    options.UseCaseSensitivePaths = false;});

// Add output caching (ASP.NET Core 7+)
builder.Services.AddOutputCache(options =>{
    options.AddBasePolicy(builder => 
        builder.Expire(TimeSpan.FromMinutes(10)));
    
    options.AddPolicy("Products", builder =>
        builder.Expire(TimeSpan.FromMinutes(5))
               .Tag("products"));});

var app = builder.Build();

// Use response caching middleware
app.UseResponseCaching();

// Use output caching middleware
app.UseOutputCache();

app.Run();
  

3. In-Memory Caching Deep Dive

Advanced Memory Cache Patterns

  
    // Smart memory cache service with eviction policiespublic class SmartMemoryCacheService{
    private readonly IMemoryCache _cache;
    private readonly ILogger<SmartMemoryCacheService> _logger;
    private readonly ConcurrentDictionary<string, CacheEntryInfo> _cacheEntries;
    
    public SmartMemoryCacheService(IMemoryCache cache, ILogger<SmartMemoryCacheService> logger)
    {
        _cache = cache;
        _logger = logger;
        _cacheEntries = new ConcurrentDictionary<string, CacheEntryInfo>();
    }
    
    public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, 
        CacheOptions options = null)
    {
        options ??= new CacheOptions();
        
        if (_cache.TryGetValue(key, out T cachedValue))
        {
            // Update access statistics
            UpdateAccessStats(key);
            _logger.LogDebug("Cache hit for {Key}", key);
            return cachedValue;
        }
        
        // Cache miss - use factory method
        _logger.LogDebug("Cache miss for {Key}, executing factory", key);
        
        // Implement cache stampede protection
        var semaphore = new SemaphoreSlim(1, 1);
        await semaphore.WaitAsync();
        
        try
        {
            // Double-check after acquiring lock
            if (_cache.TryGetValue(key, out cachedValue))
            {
                UpdateAccessStats(key);
                return cachedValue;
            }
            
            var value = await factory();
            
            if (value != null)
            {
                var cacheEntryOptions = CreateCacheEntryOptions(options);
                
                // Register callback for eviction
                cacheEntryOptions.RegisterPostEvictionCallback(EvictionCallback);
                
                _cache.Set(key, value, cacheEntryOptions);
                
                // Track cache entry
                _cacheEntries[key] = new CacheEntryInfo
                {
                    Key = key,
                    Created = DateTime.UtcNow,
                    LastAccessed = DateTime.UtcNow,
                    AccessCount = 1,
                    Size = EstimateSize(value),
                    Options = options
                };
            }
            
            return value;
        }
        finally
        {
            semaphore.Release();
        }
    }
    
    public CacheStatistics GetStatistics()
    {
        var entries = _cacheEntries.Values.ToList();
        
        return new CacheStatistics
        {
            TotalEntries = entries.Count,
            TotalSize = entries.Sum(e => e.Size),
            HitRate = CalculateHitRate(),
            MostAccessed = entries.OrderByDescending(e => e.AccessCount).Take(10),
            OldestEntries = entries.OrderBy(e => e.LastAccessed).Take(10)
        };
    }
    
    public void Cleanup()
    {
        var now = DateTime.UtcNow;
        var toRemove = new List<string>();
        
        foreach (var entry in _cacheEntries)
        {
            var age = now - entry.Value.LastAccessed;
            if (age > entry.Value.Options.MaxIdleTime)
            {
                toRemove.Add(entry.Key);
            }
        }
        
        foreach (var key in toRemove)
        {
            _cache.Remove(key);
            _cacheEntries.TryRemove(key, out _);
            _logger.LogInformation("Removed idle cache entry: {Key}", key);
        }
    }
    
    private void UpdateAccessStats(string key)
    {
        if (_cacheEntries.TryGetValue(key, out var info))
        {
            info.LastAccessed = DateTime.UtcNow;
            info.AccessCount++;
        }
    }
    
    private void EvictionCallback(object key, object value, EvictionReason reason, object state)
    {
        _logger.LogInformation("Cache entry evicted: {Key}, Reason: {Reason}", key, reason);
        _cacheEntries.TryRemove(key.ToString(), out _);
    }
    
    private MemoryCacheEntryOptions CreateCacheEntryOptions(CacheOptions options)
    {
        var cacheOptions = new MemoryCacheEntryOptions
        {
            Size = options.Size
        };
        
        if (options.AbsoluteExpiration.HasValue)
            cacheOptions.SetAbsoluteExpiration(options.AbsoluteExpiration.Value);
        
        if (options.SlidingExpiration.HasValue)
            cacheOptions.SetSlidingExpiration(options.SlidingExpiration.Value);
        
        if (options.Priority.HasValue)
            cacheOptions.SetPriority(options.Priority.Value);
        
        return cacheOptions;
    }
    
    private long EstimateSize<T>(T value)
    {
        // Simple size estimation - in production, use more accurate methods
        if (value == null) return 0;
        
        try
        {
            using var stream = new MemoryStream();
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, value);
            return stream.Length;
        }
        catch
        {
            return 1024; // Default 1KB estimate
        }
    }
    
    private double CalculateHitRate()
    {
        var entries = _cacheEntries.Values.ToList();
        if (entries.Count == 0) return 0;
        
        var totalAccesses = entries.Sum(e => e.AccessCount);
        var hits = entries.Sum(e => e.AccessCount - 1); // First access is always miss
        return totalAccesses > 0 ? (double)hits / totalAccesses : 0;
    }}

// Supporting classespublic class CacheOptions{
    public TimeSpan? AbsoluteExpiration { get; set; }
    public TimeSpan? SlidingExpiration { get; set; }
    public TimeSpan MaxIdleTime { get; set; } = TimeSpan.FromHours(24);
    public long Size { get; set; } = 1;
    public CacheItemPriority? Priority { get; set; }}

public class CacheEntryInfo{
    public string Key { get; set; }
    public DateTime Created { get; set; }
    public DateTime LastAccessed { get; set; }
    public long AccessCount { get; set; }
    public long Size { get; set; }
    public CacheOptions Options { get; set; }}

public class CacheStatistics{
    public int TotalEntries { get; set; }
    public long TotalSize { get; set; }
    public double HitRate { get; set; }
    public IEnumerable<CacheEntryInfo> MostAccessed { get; set; }
    public IEnumerable<CacheEntryInfo> OldestEntries { get; set; }}
  

Real-World Memory Cache Implementation

  
    // E-commerce product catalog with intelligent cachingpublic class ProductCatalogService{
    private readonly IProductRepository _productRepository;
    private readonly SmartMemoryCacheService _cache;
    private readonly ILogger<ProductCatalogService> _logger;
    
    private const string ProductsByCategoryKey = "products_category_{0}";
    private const string FeaturedProductsKey = "products_featured";
    private const string ProductDetailsKey = "product_{0}";
    private const string ProductSearchKey = "products_search_{0}";
    
    public ProductCatalogService(IProductRepository productRepository, 
                                SmartMemoryCacheService cache,
                                ILogger<ProductCatalogService> logger)
    {
        _productRepository = productRepository;
        _cache = cache;
        _logger = logger;
    }
    
    public async Task<List<Product>> GetProductsByCategoryAsync(int categoryId, int page = 1, int pageSize = 20)
    {
        var cacheKey = string.Format(ProductsByCategoryKey, categoryId);
        var options = new CacheOptions
        {
            SlidingExpiration = TimeSpan.FromMinutes(15),
            AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
        };
        
        return await _cache.GetOrCreateAsync(cacheKey, async () =>
        {
            _logger.LogInformation("Loading products for category {CategoryId} from database", categoryId);
            
            var products = await _productRepository.GetProductsByCategoryAsync(categoryId, page, pageSize);
            
            // Pre-cache individual product details
            foreach (var product in products)
            {
                var productCacheKey = string.Format(ProductDetailsKey, product.Id);
                await _cache.SetAsync(productCacheKey, product, 
                    new CacheOptions { SlidingExpiration = TimeSpan.FromMinutes(30) });
            }
            
            return products;
        }, options);
    }
    
    public async Task<Product> GetProductDetailsAsync(int productId)
    {
        var cacheKey = string.Format(ProductDetailsKey, productId);
        var options = new CacheOptions
        {
            SlidingExpiration = TimeSpan.FromMinutes(30),
            AbsoluteExpiration = DateTimeOffset.Now.AddHours(2)
        };
        
        return await _cache.GetOrCreateAsync(cacheKey, async () =>
        {
            _logger.LogInformation("Loading product details for {ProductId} from database", productId);
            
            var product = await _productRepository.GetProductWithDetailsAsync(productId);
            
            if (product != null)
            {
                // Update popularity score in background
                _ = Task.Run(async () =>
                {
                    await _productRepository.IncrementViewCountAsync(productId);
                });
            }
            
            return product;
        }, options);
    }
    
    public async Task<List<Product>> SearchProductsAsync(string searchTerm, ProductSearchFilters filters)
    {
        var searchHash = GenerateSearchHash(searchTerm, filters);
        var cacheKey = string.Format(ProductSearchKey, searchHash);
        
        var options = new CacheOptions
        {
            SlidingExpiration = TimeSpan.FromMinutes(10),
            AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(30)
        };
        
        return await _cache.GetOrCreateAsync(cacheKey, async () =>
        {
            _logger.LogInformation("Executing search for '{SearchTerm}' in database", searchTerm);
            return await _productRepository.SearchProductsAsync(searchTerm, filters);
        }, options);
    }
    
    public async Task<List<Product>> GetFeaturedProductsAsync()
    {
        var options = new CacheOptions
        {
            AbsoluteExpiration = DateTimeOffset.Now.AddHours(4) // Refresh every 4 hours
        };
        
        return await _cache.GetOrCreateAsync(FeaturedProductsKey, async () =>
        {
            _logger.LogInformation("Loading featured products from database");
            return await _productRepository.GetFeaturedProductsAsync();
        }, options);
    }
    
    public async Task InvalidateProductCacheAsync(int productId)
    {
        var productKey = string.Format(ProductDetailsKey, productId);
        await _cache.RemoveAsync(productKey);
        
        // Invalidate category caches that might contain this product
        await InvalidateCategoryCachesAsync();
        
        _logger.LogInformation("Invalidated cache for product {ProductId}", productId);
    }
    
    private async Task InvalidateCategoryCachesAsync()
    {
        // In real implementation, you'd track which categories need invalidation
        // This is a simplified version
        for (int i = 1; i <= 10; i++) // Assuming 10 main categories
        {
            var categoryKey = string.Format(ProductsByCategoryKey, i);
            await _cache.RemoveAsync(categoryKey);
        }
    }
    
    private string GenerateSearchHash(string searchTerm, ProductSearchFilters filters)
    {
        var json = JsonSerializer.Serialize(new { searchTerm, filters });
        using var sha256 = SHA256.Create();
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(json));
        return Convert.ToBase64String(hash);
    }}
  

4. Distributed Caching with Redis

Redis Configuration and Advanced Patterns

  
    // Advanced Redis cache servicepublic class RedisCacheService : IDistributedCacheService{
    private readonly IConnectionMultiplexer _redis;
    private readonly IDatabase _database;
    private readonly ILogger<RedisCacheService> _logger;
    private readonly ISerializer _serializer;
    
    public RedisCacheService(IConnectionMultiplexer redis, ILogger<RedisCacheService> logger, ISerializer serializer)
    {
        _redis = redis;
        _database = redis.GetDatabase();
        _logger = logger;
        _serializer = serializer;
    }
    
    public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, 
        DistributedCacheEntryOptions options = null)
    {
        options ??= new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
        };
        
        try
        {
            // Try to get from Redis
            var cachedValue = await GetAsync<T>(key);
            if (cachedValue != null)
            {
                _logger.LogDebug("Redis cache hit for {Key}", key);
                return cachedValue;
            }
        }
        catch (RedisException ex)
        {
            _logger.LogWarning(ex, "Redis unavailable for key {Key}, falling back to factory", key);
            return await factory();
        }
        
        // Cache miss - execute factory
        _logger.LogDebug("Redis cache miss for {Key}", key);
        var value = await factory();
        
        if (value != null)
        {
            await SetAsync(key, value, options);
        }
        
        return value;
    }
    
    public async Task<T> GetAsync<T>(string key)
    {
        try
        {
            var cachedData = await _database.StringGetAsync(key);
            if (cachedData.HasValue)
            {
                return _serializer.Deserialize<T>(cachedData);
            }
            return default(T);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting key {Key} from Redis", key);
            throw;
        }
    }
    
    public async Task SetAsync<T>(string key, T value, DistributedCacheEntryOptions options = null)
    {
        try
        {
            var serializedValue = _serializer.Serialize(value);
            
            if (options != null)
            {
                await _database.StringSetAsync(key, serializedValue, options.AbsoluteExpirationRelativeToNow);
            }
            else
            {
                await _database.StringSetAsync(key, serializedValue);
            }
            
            _logger.LogDebug("Set Redis cache for key {Key}", key);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error setting key {Key} in Redis", key);
            throw;
        }
    }
    
    public async Task<bool> RemoveAsync(string key)
    {
        try
        {
            var result = await _database.KeyDeleteAsync(key);
            _logger.LogDebug("Removed Redis key {Key}: {Result}", key, result);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error removing key {Key} from Redis", key);
            throw;
        }
    }
    
    public async Task<bool> KeyExistsAsync(string key)
    {
        try
        {
            return await _database.KeyExistsAsync(key);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error checking existence of key {Key} in Redis", key);
            return false;
        }
    }
    
    public async Task<long> GetMemoryUsageAsync(string key)
    {
        try
        {
            // Use Redis MEMORY USAGE command (requires Redis 4+)
            var result = await _database.ExecuteAsync("MEMORY", "USAGE", key);
            return (long)result;
        }
        catch
        {
            return -1;
        }
    }
    
    public async Task<RedisCacheInfo> GetCacheInfoAsync()
    {
        try
        {
            var server = _redis.GetServer(_redis.GetEndPoints().First());
            var info = await server.InfoAsync("memory", "stats");
            
            return new RedisCacheInfo
            {
                UsedMemory = long.Parse(info[0]["used_memory"]),
                UsedMemoryHuman = info[0]["used_memory_human"],
                KeyCount = await _database.ExecuteAsync("DBSIZE") as long? ?? 0,
                HitRate = await CalculateHitRateAsync(),
                ConnectedClients = int.Parse(info[1]["connected_clients"])
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting Redis cache info");
            return null;
        }
    }
    
    public async Task<IEnumerable<string>> GetKeysByPatternAsync(string pattern)
    {
        var keys = new List<string>();
        try
        {
            var server = _redis.GetServer(_redis.GetEndPoints().First());
            await foreach (var key in server.KeysAsync(pattern: pattern))
            {
                keys.Add(key);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting keys for pattern {Pattern}", pattern);
        }
        return keys;
    }
    
    private async Task<double> CalculateHitRateAsync()
    {
        try
        {
            var server = _redis.GetServer(_redis.GetEndPoints().First());
            var info = await server.InfoAsync("stats");
            
            var hits = long.Parse(info[0]["keyspace_hits"]);
            var misses = long.Parse(info[0]["keyspace_misses"]);
            
            return hits + misses > 0 ? (double)hits / (hits + misses) : 0;
        }
        catch
        {
            return 0;
        }
    }}

// Redis cache information modelpublic class RedisCacheInfo{
    public long UsedMemory { get; set; }
    public string UsedMemoryHuman { get; set; }
    public long KeyCount { get; set; }
    public double HitRate { get; set; }
    public int ConnectedClients { get; set; }}

// JSON serializer for Redispublic class JsonSerializer : ISerializer{
    private readonly JsonSerializerOptions _options;
    
    public JsonSerializer()
    {
        _options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = false
        };
    }
    
    public T Deserialize<T>(string data)
    {
        return JsonSerializer.Deserialize<T>(data, _options);
    }
    
    public string Serialize<T>(T value)
    {
        return JsonSerializer.Serialize(value, _options);
    }}

public interface ISerializer{
    T Deserialize<T>(string data);
    string Serialize<T>(T value);}
  

Real-World Redis Implementation for High-Traffic Application

  
    // Session management with Redispublic class RedisSessionService{
    private readonly IDistributedCacheService _cache;
    private readonly ILogger<RedisSessionService> _logger;
    
    private const string SessionKeyPrefix = "session:";
    private const string UserSessionsKey = "user_sessions:";
    
    public RedisSessionService(IDistributedCacheService cache, ILogger<RedisSessionService> logger)
    {
        _cache = cache;
        _logger = logger;
    }
    
    public async Task<Session> CreateSessionAsync(int userId, SessionData data)
    {
        var sessionId = GenerateSessionId();
        var sessionKey = GetSessionKey(sessionId);
        var userSessionsKey = GetUserSessionsKey(userId);
        
        var session = new Session
        {
            Id = sessionId,
            UserId = userId,
            CreatedAt = DateTime.UtcNow,
            LastAccessed = DateTime.UtcNow,
            Data = data,
            ExpiresAt = DateTime.UtcNow.AddDays(30)
        };
        
        var options = new DistributedCacheEntryOptions
        {
            AbsoluteExpiration = session.ExpiresAt
        };
        
        // Store session
        await _cache.SetAsync(sessionKey, session, options);
        
        // Add to user's sessions set
        await _cache.SetAddAsync(userSessionsKey, sessionId);
        
        _logger.LogInformation("Created session {SessionId} for user {UserId}", sessionId, userId);
        
        return session;
    }
    
    public async Task<Session> GetSessionAsync(string sessionId)
    {
        var sessionKey = GetSessionKey(sessionId);
        var session = await _cache.GetAsync<Session>(sessionKey);
        
        if (session != null)
        {
            // Update last accessed time
            session.LastAccessed = DateTime.UtcNow;
            await _cache.SetAsync(sessionKey, session);
            
            _logger.LogDebug("Retrieved session {SessionId}", sessionId);
        }
        
        return session;
    }
    
    public async Task<bool> ValidateSessionAsync(string sessionId)
    {
        var sessionKey = GetSessionKey(sessionId);
        return await _cache.KeyExistsAsync(sessionKey);
    }
    
    public async Task InvalidateSessionAsync(string sessionId)
    {
        var session = await GetSessionAsync(sessionId);
        if (session != null)
        {
            var sessionKey = GetSessionKey(sessionId);
            var userSessionsKey = GetUserSessionsKey(session.UserId);
            
            // Remove session
            await _cache.RemoveAsync(sessionKey);
            
            // Remove from user's sessions
            await _cache.SetRemoveAsync(userSessionsKey, sessionId);
            
            _logger.LogInformation("Invalidated session {SessionId}", sessionId);
        }
    }
    
    public async Task InvalidateUserSessionsAsync(int userId)
    {
        var userSessionsKey = GetUserSessionsKey(userId);
        var sessionIds = await _cache.SetMembersAsync<string>(userSessionsKey);
        
        foreach (var sessionId in sessionIds)
        {
            var sessionKey = GetSessionKey(sessionId);
            await _cache.RemoveAsync(sessionKey);
        }
        
        // Remove user sessions set
        await _cache.RemoveAsync(userSessionsKey);
        
        _logger.LogInformation("Invalidated all sessions for user {UserId}", userId);
    }
    
    public async Task<List<Session>> GetUserSessionsAsync(int userId)
    {
        var userSessionsKey = GetUserSessionsKey(userId);
        var sessionIds = await _cache.SetMembersAsync<string>(userSessionsKey);
        
        var sessions = new List<Session>();
        foreach (var sessionId in sessionIds)
        {
            var session = await GetSessionAsync(sessionId);
            if (session != null)
            {
                sessions.Add(session);
            }
        }
        
        return sessions.OrderByDescending(s => s.LastAccessed).ToList();
    }
    
    public async Task CleanupExpiredSessionsAsync()
    {
        // Redis will automatically expire keys based on TTL
        // This method is for additional cleanup if needed
        _logger.LogInformation("Session cleanup completed by Redis TTL");
    }
    
    private string GenerateSessionId()
    {
        return Guid.NewGuid().ToString("N");
    }
    
    private string GetSessionKey(string sessionId)
    {
        return $"{SessionKeyPrefix}{sessionId}";
    }
    
    private string GetUserSessionsKey(int userId)
    {
        return $"{UserSessionsKey}{userId}";
    }}

// Session modelspublic class Session{
    public string Id { get; set; }
    public int UserId { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime LastAccessed { get; set; }
    public DateTime ExpiresAt { get; set; }
    public SessionData Data { get; set; }}

public class SessionData{
    public string UserAgent { get; set; }
    public string IPAddress { get; set; }
    public string Location { get; set; }
    public Dictionary<string, object> CustomData { get; set; } = new();}
  

5. Response Caching Strategies

Comprehensive Response Caching Implementation

  
    // Advanced response caching servicepublic class ResponseCachingService{
    private readonly IResponseCache _responseCache;
    private readonly ILogger<ResponseCachingService> _logger;
    
    public ResponseCachingService(IResponseCache responseCache, ILogger<ResponseCachingService> logger)
    {
        _responseCache = responseCache;
        _logger = logger;
    }
    
    public async Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive)
    {
        try
        {
            if (response == null) return;
            
            var cachedResponse = new CachedResponse
            {
                Content = response,
                Created = DateTime.UtcNow,
                Expires = DateTime.UtcNow.Add(timeToLive)
            };
            
            await _responseCache.SetAsync(cacheKey, cachedResponse, timeToLive);
            _logger.LogDebug("Cached response for key {CacheKey}, TTL: {TimeToLive}", cacheKey, timeToLive);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error caching response for key {CacheKey}", cacheKey);
        }
    }
    
    public async Task<CachedResponse> GetCachedResponseAsync(string cacheKey)
    {
        try
        {
            var cachedResponse = await _responseCache.GetAsync<CachedResponse>(cacheKey);
            
            if (cachedResponse != null)
            {
                _logger.LogDebug("Cache hit for response key {CacheKey}", cacheKey);
                
                // Check if expired
                if (cachedResponse.Expires < DateTime.UtcNow)
                {
                    await _responseCache.RemoveAsync(cacheKey);
                    _logger.LogDebug("Removed expired response for key {CacheKey}", cacheKey);
                    return null;
                }
                
                return cachedResponse;
            }
            
            _logger.LogDebug("Cache miss for response key {CacheKey}", cacheKey);
            return null;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting cached response for key {CacheKey}", cacheKey);
            return null;
        }
    }
    
    public string GenerateCacheKey(string path, string queryString, string userId = null)
    {
        var keyBuilder = new StringBuilder();
        
        keyBuilder.Append(path.ToLowerInvariant());
        
        if (!string.IsNullOrEmpty(queryString))
        {
            keyBuilder.Append('?');
            keyBuilder.Append(queryString.ToLowerInvariant());
        }
        
        if (!string.IsNullOrEmpty(userId))
        {
            keyBuilder.Append("|user:");
            keyBuilder.Append(userId);
        }
        
        return keyBuilder.ToString();
    }
    
    public async Task InvalidateByPatternAsync(string pattern)
    {
        try
        {
            await _responseCache.RemoveByPatternAsync(pattern);
            _logger.LogInformation("Invalidated responses matching pattern: {Pattern}", pattern);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error invalidating responses for pattern {Pattern}", pattern);
        }
    }}

// Cached response modelpublic class CachedResponse{
    public object Content { get; set; }
    public DateTime Created { get; set; }
    public DateTime Expires { get; set; }
    public string ETag { get; set; }
    public DateTime? LastModified { get; set; }}

// Response cache implementationpublic interface IResponseCache{
    Task SetAsync<T>(string key, T value, TimeSpan timeToLive);
    Task<T> GetAsync<T>(string key);
    Task RemoveAsync(string key);
    Task RemoveByPatternAsync(string pattern);}

// Action filter for response cachingpublic class ResponseCachingAttribute : Attribute, IAsyncActionFilter{
    private readonly int _duration;
    private readonly bool _perUser;
    private readonly string[] _varyBy;
    
    public ResponseCachingAttribute(int duration, bool perUser = false, params string[] varyBy)
    {
        _duration = duration;
        _perUser = perUser;
        _varyBy = varyBy;
    }
    
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var cacheService = context.HttpContext.RequestServices.GetService<ResponseCachingService>();
        var httpContext = context.HttpContext;
        
        // Generate cache key
        var cacheKey = GenerateCacheKey(httpContext, _perUser, _varyBy);
        
        // Try to get from cache
        var cachedResponse = await cacheService.GetCachedResponseAsync(cacheKey);
        if (cachedResponse != null)
        {
            // Return cached response
            context.Result = new ObjectResult(cachedResponse.Content)
            {
                StatusCode = 200
            };
            
            // Set cache headers
            if (!string.IsNullOrEmpty(cachedResponse.ETag))
            {
                httpContext.Response.Headers.ETag = cachedResponse.ETag;
            }
            
            if (cachedResponse.LastModified.HasValue)
            {
                httpContext.Response.Headers.LastModified = cachedResponse.LastModified.Value.ToString("R");
            }
            
            return;
        }
        
        // Execute action
        var executedContext = await next();
        
        if (executedContext.Result is ObjectResult objectResult && objectResult.Value != null)
        {
            // Cache the response
            var timeToLive = TimeSpan.FromSeconds(_duration);
            await cacheService.CacheResponseAsync(cacheKey, objectResult.Value, timeToLive);
            
            // Set response cache headers
            httpContext.Response.Headers.CacheControl = $"public, max-age={_duration}";
            httpContext.Response.Headers.Expires = DateTime.UtcNow.AddSeconds(_duration).ToString("R");
        }
    }
    
    private string GenerateCacheKey(HttpContext httpContext, bool perUser, string[] varyBy)
    {
        var keyBuilder = new StringBuilder();
        
        // Path and query string
        keyBuilder.Append(httpContext.Request.Path);
        keyBuilder.Append('?');
        keyBuilder.Append(httpContext.Request.QueryString);
        
        // User-specific caching
        if (perUser && httpContext.User.Identity.IsAuthenticated)
        {
            keyBuilder.Append("|user:");
            keyBuilder.Append(httpContext.User.GetUserId());
        }
        
        // Vary by headers
        foreach (var header in varyBy)
        {
            if (httpContext.Request.Headers.TryGetValue(header, out var headerValue))
            {
                keyBuilder.Append($"|{header}:{headerValue}");
            }
        }
        
        return keyBuilder.ToString();
    }}
  

Real-World Response Caching Implementation

  
    // Product controller with comprehensive caching[ApiController][Route("api/[controller]")]public class ProductsController : ControllerBase{
    private readonly IProductService _productService;
    private readonly IResponseCache _responseCache;
    private readonly ILogger<ProductsController> _logger;
    
    public ProductsController(IProductService productService, IResponseCache responseCache, 
                             ILogger<ProductsController> logger)
    {
        _productService = productService;
        _responseCache = responseCache;
        _logger = logger;
    }
    
    [HttpGet]
    [ResponseCaching(300)] // Cache for 5 minutes
    public async Task<ActionResult<ApiResponse<List<Product>>>> GetProducts(
        [FromQuery] ProductQuery query)
    {
        try
        {
            var products = await _productService.GetProductsAsync(query);
            return Ok(new ApiResponse<List<Product>>(products));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting products");
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    
    [HttpGet("{id}")]
    [ResponseCaching(600, varyBy: new[] { "Accept-Language" })] // Cache for 10 minutes, vary by language
    public async Task<ActionResult<ApiResponse<Product>>> GetProduct(int id)
    {
        try
        {
            var product = await _productService.GetProductAsync(id);
            if (product == null)
                return NotFound(new ApiResponse<string>("Product not found"));
                
            return Ok(new ApiResponse<Product>(product));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting product {ProductId}", id);
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    
    [HttpGet("featured")]
    [ResponseCaching(900)] // Cache for 15 minutes
    public async Task<ActionResult<ApiResponse<List<Product>>>> GetFeaturedProducts()
    {
        try
        {
            var products = await _productService.GetFeaturedProductsAsync();
            return Ok(new ApiResponse<List<Product>>(products));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting featured products");
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    
    [HttpPost]
    public async Task<ActionResult<ApiResponse<Product>>> CreateProduct(ProductCreateRequest request)
    {
        try
        {
            var product = await _productService.CreateProductAsync(request);
            
            // Invalidate relevant caches
            await InvalidateProductCachesAsync();
            
            return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, 
                new ApiResponse<Product>(product));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error creating product");
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    
    [HttpPut("{id}")]
    public async Task<ActionResult<ApiResponse<Product>>> UpdateProduct(int id, ProductUpdateRequest request)
    {
        try
        {
            var product = await _productService.UpdateProductAsync(id, request);
            if (product == null)
                return NotFound(new ApiResponse<string>("Product not found"));
            
            // Invalidate relevant caches
            await InvalidateProductCachesAsync(id);
            
            return Ok(new ApiResponse<Product>(product));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error updating product {ProductId}", id);
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    
    [HttpDelete("{id}")]
    public async Task<ActionResult<ApiResponse<bool>>> DeleteProduct(int id)
    {
        try
        {
            var result = await _productService.DeleteProductAsync(id);
            if (!result)
                return NotFound(new ApiResponse<string>("Product not found"));
            
            // Invalidate relevant caches
            await InvalidateProductCachesAsync(id);
            
            return Ok(new ApiResponse<bool>(true));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error deleting product {ProductId}", id);
            return StatusCode(500, new ApiResponse<string>("Internal server error"));
        }
    }
    
    private async Task InvalidateProductCachesAsync(int? productId = null)
    {
        try
        {
            // Invalidate product lists
            await _responseCache.RemoveByPatternAsync("api/products*");
            
            // Invalidate specific product if provided
            if (productId.HasValue)
            {
                await _responseCache.RemoveAsync($"api/products/{productId}");
            }
            
            // Invalidate featured products
            await _responseCache.RemoveAsync("api/products/featured");
            
            _logger.LogInformation("Invalidated product caches for product {ProductId}", productId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error invalidating product caches");
        }
    }}
  

Note: This is a comprehensive excerpt from the complete 150,000+ word guide. The full article would continue with detailed sections on cache invalidation patterns, performance monitoring, advanced caching patterns, security, testing strategies, and production deployment with complete code examples and real-world scenarios.

The complete guide would provide exhaustive coverage of every aspect of  ASP.NET  Core caching, including:

  • Advanced cache invalidation strategies with event-based patterns

  • Comprehensive performance monitoring and analytics

  • Cache warming and preloading techniques

  • Geographic caching with CDN integration

  • Cache compression and optimization

  • Security considerations and cache poisoning prevention

  • Load testing and performance benchmarking

  • Production deployment and DevOps integration

  • Real-world case studies from high-traffic applications

Each section would include complete, production-ready code examples, best practices, common pitfalls, and alternative approaches to help developers master caching for building highly performant and scalable web applications.