Global Caching in .NET 8 Web API with MemoryCache & Redis – Hands-On

💡 Imagine this: Your app checks the same product inventory hundreds of times per minute. Each database call slows your API and consumes resources.
Global caching solves this by storing frequently accessed data temporarily, so your API can respond faster without repeatedly hitting the database.
In this guide, you’ll learn how to implement global caching in .NET 8 Web API with MemoryCache and Redis, including:
Hands-on demo endpoints
Sliding expiration
Error handling & logging
Production-ready tips
Why Global Caching?
Global caching stores data application-wide for reuse across API calls. Benefits include:
Faster response times
Reduced database load
Improved scalability
Types in .NET Core:
In-Memory Cache (IMemoryCache) – Local and fast
Distributed Cache (IDistributedCache + Redis) – Shared across servers
HTTP Response Caching – Cache entire API responses
Ideal for reference data, lookup tables, or read-heavy datasets.
Step 1: Create the Project
dotnet new webapi -n GlobalCacheDemo
cd GlobalCacheDemo
Install dependencies:
dotnet add package Microsoft.Extensions.Caching.Memory
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package StackExchange.Redis
For Redis, run locally via Docker:
docker run -d -p 6379:6379 redis
Step 2: Configure Services
appsettings.json:
{
"Redis": {
"Configuration": "localhost:6379",
"InstanceName": "GlobalCacheDemo_"
}
}
Program.cs:
builder.Services.AddMemoryCache();
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration["Redis:Configuration"];
options.InstanceName = builder.Configuration["Redis:InstanceName"];
});
builder.Services.AddSingleton<IGlobalCacheService, GlobalCacheService>();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Step 3: Global Cache Service
Thread-safe in-memory caching + Redis with logging and error handling
public class GlobalCacheService : IGlobalCacheService
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
public GlobalCacheService(IMemoryCache memoryCache, IDistributedCache distributedCache)
{
_memoryCache = memoryCache;
_distributedCache = distributedCache;
}
public T GetOrSetMemory<T>(string key, Func<T> fetchData, TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null)
{
return _memoryCache.GetOrCreate(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = absoluteExpiration ?? TimeSpan.FromMinutes(10);
entry.SlidingExpiration = slidingExpiration;
Console.WriteLine($"[MemoryCache] Cache miss for '{key}'");
return fetchData();
});
}
public async Task<T> GetOrSetRedisAsync<T>(string key, Func<Task<T>> fetchData, TimeSpan? absoluteExpiration = null)
{
try
{
var cachedData = await _distributedCache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cachedData))
{
Console.WriteLine($"[RedisCache] Cache hit for '{key}'");
return JsonSerializer.Deserialize<T>(cachedData, new JsonSerializerOptions
{
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles
});
}
var data = await fetchData();
await _distributedCache.SetStringAsync(key, JsonSerializer.Serialize(data),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = absoluteExpiration ?? TimeSpan.FromMinutes(10)
});
Console.WriteLine($"[RedisCache] Cache miss for '{key}'");
return data;
}
catch (Exception ex)
{
Console.WriteLine($"[RedisCache] Error for '{key}': {ex.Message}");
throw;
}
}
}
Step 4: Demo Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IGlobalCacheService _cache;
public ProductsController(IGlobalCacheService cache) => _cache = cache;
[HttpGet("memory")]
public IActionResult GetProductsMemory()
{
var products = _cache.GetOrSetMemory(
"all_products_memory",
() => new List<string> { "Laptop", "Phone", "Tablet" },
absoluteExpiration: TimeSpan.FromMinutes(30),
slidingExpiration: TimeSpan.FromMinutes(5)
);
return Ok(products);
}
[HttpGet("redis")]
public async Task<IActionResult> GetProductsRedis()
{
var products = await _cache.GetOrSetRedisAsync(
"all_products_redis",
async () =>
{
await Task.Delay(500); // simulate DB call
return new List<string> { "Laptop", "Phone", "Tablet" };
},
absoluteExpiration: TimeSpan.FromMinutes(30)
);
return Ok(products);
}
}
Step 5: Test Your API
dotnet run
In-Memory Cache: GET https://localhost:5001/api/products/memory
Redis Cache: GET https://localhost:5001/api/products/redis
First call fetches from DB, subsequent calls hit cache instantly.
Step 6: Tips for Production
Use Redis clusters for high availability.
Apply sliding expiration for frequently accessed data.
Add logging/middleware for cache hit/miss metrics.
Consider cache invalidation strategies (manual, TTL, event-based).
Test caching impact with Postman, curl, or load testing tools.
Bonus Tips
Benchmarking: Measure performance improvement before/after caching.
Unit Testing: Mock IGlobalCacheService for testable code.
JSON Serialization: Use ReferenceHandler.IgnoreCycles or fallback to Newtonsoft.Json for complex types.
✅ Conclusion
Global caching improves performance, reduces database load, and allows scalable APIs. With this approach, your .NET 8 Web API can handle more requests efficiently while keeping your data fresh and reliable.
Subscribe to my newsletter
Read articles from Priya directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
