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

PriyaPriya
4 min read

💡 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

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.

0
Subscribe to my newsletter

Read articles from Priya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Priya
Priya