Caching in .NET C#: In-Memory , Distributed and Hybrid Caching explained

Zied Rebhi
4 min readDec 8, 2024

--

Caching in .NET C#: In-Memory , Distributed and Hybrid Caching

Caching is a vital optimization technique in application development.It helps improve performance and scalability by reducing the needs for repetitive computation or data retrieval. In this guide, we’ll explore In-Memory, Distributed, and Hybrid Caching in .NET 9.

Introduction to Caching

Caching involves temporarily storing data in a fast-access storage layer to improve response times and reduce system loading. In .NET 9, you can use built-in caching mechanisms to implement various caching strategies:

1. In-Memory Caching

What is it?

In-Memory Caching stores data in the server’s RAM, making it fast. However, it is limited to a single server and is unsuitable for distributed systems where multiple servers are involved.

Use Case

A weather application that fetches frequently accessed forecasts can use in-memory caching to store responses temporarily.

Implementation Example

Step 1: Create the CacheManager Class

Encapsulate the caching logic in a reusable class.

using Microsoft.Extensions.Caching.Memory;

public class CacheManager<T>
{
private readonly IMemoryCache _memoryCache;

public CacheManager(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}

public T GetOrSet(string key, Func<T> fetchFunction, TimeSpan cacheDuration)
{
if (!_memoryCache.TryGetValue(key, out T value))
{
value = fetchFunction();
_memoryCache.Set(key, value, cacheDuration);
}

return value;
}
}

Step 2: Create a Service Class

Fetch data and apply caching.

public class WeatherService
{
private readonly CacheManager<string> _cacheManager;

public WeatherService(CacheManager<string> cacheManager)
{
_cacheManager = cacheManager;
}

public string GetWeather(string city)
{
string cacheKey = $"Weather_{city}";
return _cacheManager.GetOrSet(cacheKey, () => FetchWeatherFromAPI(city), TimeSpan.FromMinutes(30));
}

private string FetchWeatherFromAPI(string city)
{
// Simulated external API call
return $"{city}: 25°C, Sunny";
}
}

Step 3: Use the Service in a Controller

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
private readonly WeatherService _weatherService;

public WeatherController(WeatherService weatherService)
{
_weatherService = weatherService;
}

[HttpGet("{city}")]
public IActionResult GetWeather(string city)
{
var weather = _weatherService.GetWeather(city);
return Ok(weather);
}
}

Register Dependencies in Program.cs

builder.Services.AddMemoryCache();
builder.Services.AddScoped(typeof(CacheManager<>));
builder.Services.AddScoped<WeatherService>();

2. Distributed Caching

What is it?

Distributed Caching stores data in a centralized external system (e.g., Redis, SQL Server),allowing all servers in a distributed system to share the same cache.

Use Case

An online store that fetches product inventory informations can use distributed caching to ensure consistency across servers.

Implementation Example: Using Redis

Step 1: Configure Redis in Program.cs

builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379"; // Replace with your Redis configuration
});

Step 2: Create the CacheManager Class

using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;

public class DistributedCacheManager<T>
{
private readonly IDistributedCache _distributedCache;

public DistributedCacheManager(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}

public async Task<T> GetOrSetAsync(string key, Func<Task<T>> fetchFunction, TimeSpan cacheDuration)
{
var cachedData = await _distributedCache.GetStringAsync(key);

if (cachedData != null)
{
return JsonSerializer.Deserialize<T>(cachedData)!;
}

var data = await fetchFunction();
var serializedData = JsonSerializer.Serialize(data);

await _distributedCache.SetStringAsync(
key,
serializedData,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = cacheDuration
});

return data;
}
}

Step 3: Create a Service Class

public class ProductService
{
private readonly DistributedCacheManager<string> _cacheManager;

public ProductService(DistributedCacheManager<string> cacheManager)
{
_cacheManager = cacheManager;
}

public async Task<string> GetProductAsync(int productId)
{
string cacheKey = $"Product_{productId}";
return await _cacheManager.GetOrSetAsync(cacheKey, async () =>
{
// Simulated database call
await Task.Delay(500);
return $"Product {productId}: Fancy Gadget";
}, TimeSpan.FromMinutes(10));
}
}

Step 4: Use the Service in a Controller

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly ProductService _productService;

public ProductsController(ProductService productService)
{
_productService = productService;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
var product = await _productService.GetProductAsync(id);
return Ok(product);
}
}

3. Hybrid Caching

What is it?

Hybrid Caching combines the speed of in-memory caching with the scalability of distributed caching. Data is first checked in-memory and, if unavailable, fetched from the distributed cache.

Implementation Example

Step 1: Extend CacheManager for Hybrid Logic

public class HybridCacheManager<T>
{
private HybridCache _cache;
public HybridCacheManager(HybridCache cache)
{
_cache = cache;
}
public async Task<string> GetCachedDataAsync(string key, CancellationToken token = default)
{
return await _cache.GetOrCreateAsync(
$"{key}",
async cancel => await GetDataAsync(key, cancel),
token: token
);
}
private async Task<string> GetDataAsync(string key, CancellationToken token)
{
return $"Data for cache entry with key: {key}";
}
}

Register Dependencies in Program.cs

//builder.Services.AddHybridCache();
builder.Services.AddHybridCache(options =>
{
options.MaximumPayloadBytes = 1024 * 1024;
options.MaximumKeyLength = 1024;
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(3),
LocalCacheExpiration = TimeSpan.FromMinutes(3)
};
});

Step 2: Use HybridCacheManager in a Service

public class AnalyticsService
{
private readonly HybridCacheManager<string> _cacheManager;

public AnalyticsService(HybridCacheManager<string> cacheManager)
{
_cacheManager = cacheManager;
}

public async Task<string> GetAnalyticsAsync()
{
string cacheKey = "DailyAnalytics";
return await _cacheManager.GetOrSetAsync(cacheKey, async () =>
{
// Simulate analytics calculation
await Task.Delay(1000);
return "Daily Active Users: 5000";
}, TimeSpan.FromMinutes(15));
}
}

Best Practices

  1. Choose the Right Caching Strategy: Use in-memory for single-server setups, distributed for scalability, and hybrid for a balance of speed and consistency.
  2. Set Appropriate Expiry Policies: Avoid stale data by setting realistic cache durations.
  3. Monitor Cache Usage: Use tools like Redis insights or application logs to monitor cache performance.

Conclusion

This article covered the three primary caching techniques in .NET 9 — In-Memory, Distributed, and Hybrid — using real-world examples with clean and modular code. By implementing these strategies, you can optimize your application’s performance and scalability effectively. 🚀

--

--

Zied Rebhi
Zied Rebhi

No responses yet