August 10 2024
Sponsored
• Transform your API development process with Postman Flows! Experience a new way to visually create, debug, and automate complex API workflows with ease. Dive into the future of API management and enhance your productivity here.
Many thanks to the sponsors who make it possible for this newsletter to be free for readers. Become a sponsor.
.NET memory caching is a feature used to store objects in memory for faster access. This can significantly improve the performance of applications, especially those that frequently access data from databases, web services, or other time-consuming data retrieval sources.
Types of Caching
- In-Memory Caching: Data is stored in the memory of the web server. It's fast but limited to the server's memory capacity.
- Distributed Caching: Data is stored across multiple servers. This is useful in web farm scenarios where there are multiple servers.
Caching Key components
- Cache Key: A unique identifier for each cache entry. - Cache Value: The data stored in the cache. This can be of any object type. - Expiration Policy: Determines how long an item stays in the cache. Can be absolute (fixed duration) or sliding (reset on access). - Priority: Determines the order in which items are removed from the cache under memory pressure.

.NET provides various ways to implement caching, but the most common are:
• MemoryCache Class (System.Runtime.Caching): Used for in-memory caching. It's part of the .NET Framework and .NET Core.
• IMemoryCache (Microsoft.Extensions.Caching.Memory): Introduced in .NET Core, it offers more features and better performance compared to MemoryCache.
In this issue I'll talk about IMemoryCache.
What are the key features of this library?
1. Strongly Typed: Unlike the earlier MemoryCache, IMemoryCache is strongly typed, reducing errors and improving code readability.
2. Dependency Injection Friendly: IMemoryCache is designed to be used with dependency injection, making it easy to manage and test.
3. Expiration Policies:
- Absolute Expiration: The cache entry will expire after a set duration.
- Sliding Expiration: The cache entry will expire if it's not accessed within a set duration.
- Expiration Tokens: These allow cache entries to be evicted based on external triggers, like file changes.
4. Size Limitations: You can set a size limit for the cache and define the size for each entry, helping to manage memory usage effectively.
5. Eviction Callbacks: Callbacks can be registered to execute custom logic when a cache entry is evicted.
6. Thread Safety: IMemoryCache is thread-safe, allowing concurrent access by multiple threads.
Nice. Now when we know the basics about IMemoryCache, let me show how to implement it.
The first thing we need to do is to add nuget package, of course:
dotnet add package Microsoft.Extensions.Caching.Memory
To successfully use the library, we need to add it through Dependency Injection in Program.cs class:
builder.Services.AddMemoryCache();
And you can use it with practically no configuration. I will inject it directly into the controller that .NET created for me by default.
private readonly IMemoryCache _cacheService; public WeatherForecastController(IMemoryCache cacheService){ _cacheService = cacheService;}
And now in the GET method where we capture the weather forecast from various weather stations, we will include cached ones. We understand that the weather will not change every minute, so we can cache the weather forecast values for a while.
[HttpGet(Name = "GetWeatherForecast")]public List<WeatherForecast> Get(){ if (!_cacheService.TryGetValue("MyCacheKey", out List<WeatherForecast> weathersFromCache)) { var weathers = Enumerable.Range(1, 300).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }).ToList(); _cacheService.Set<List<WeatherForecast>>("MyCacheKey", weathers); return weathers; } return weathersFromCache;}
Explanation:
• Cache Check _casheService.TryGetValue...:
• Cache Miss Handling:
• Returning the Result:
If the data was retrieved from the cache, it is returned directly.If the data was not in the cache (cache miss), the newly generated list is returned.
In the provided code snippet, there is no explicit cache expiration time set. This means the cache entry for "MyCacheKey" will remain in the cache indefinitely, or until it is explicitly removed or replaced, or if the cache evicts it due to memory pressure.
To set an explicit cache expiration time, you would need to modify the code where the cache entry is set.
This involves using MemoryCacheEntryOptions to specify the desired expiration policy.
There are two common ways to set expiration:
• Absolute Expiration: The cache entry will expire after a specified duration regardless of whether it is accessed or not.
• Sliding Expiration: The cache entry will expire after a specified duration if it is not accessed within that timeframe. Each access to the cache entry resets the expiration timer.
Let's see both in action.
Setting Absolute Expiration
Here's how you can modify the code to set an absolute expiration of, for example, 30 minutes:
var cacheEntryOptions = new MemoryCacheEntryOptions{ AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) // Cache for 30 minutes}; _cacheService.Set<List<WeatherForecast>>("MyCacheKey", weathers, cacheEntryOptions);
When to use?
Setting Sliding Expiration
And here's how you can set a sliding expiration of 30 minutes:
var cacheEntryOptions = new MemoryCacheEntryOptions{ SlidingExpiration = TimeSpan.FromMinutes(30) // Cache for 30 minutes}; _cacheService.Set<List<WeatherForecast>>("MyCacheKey", weathers, cacheEntryOptions);
When to use?
Combining Both
You can also combine both absolute and sliding expirations for more complex caching scenarios:
var cacheEntryOptions = new MemoryCacheEntryOptions{ AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), // Absolute expiration of 1 hour SlidingExpiration = TimeSpan.FromMinutes(30) // Sliding expiration of 30 minutes}; _cacheService.Set<List<WeatherForecast>>("MyCacheKey", weathers, cacheEntryOptions);
In this combined scenario, the cache entry will expire if it's not accessed for 30 minutes, but it will also expire unconditionally after 1 hour from when it was added to the cache.
MemoryCacheEntryOptions is a versatile class in .NET Core's caching infrastructure that allows you to configure various aspects of how individual items are cached in memory.
This configuration plays a crucial role in optimizing the performance and resource management of your application.
Here's a more detailed look at what you can set using MemoryCacheEntryOptions besides Expiration Settings:
CacheItemPriority: Determines the priority of a cache entry. During memory pressure, entries with lower priority may be removed first to free up space. Values range from Low to NeverRemove.
Specifies the size of the cache entry, which is considered when the cache size limit is set on the IMemoryCache instance. This helps in controlling the memory footprint of the cache.
• PostEvictionCallbacks: A list of callbacks that will be executed after the cache entry is removed. This is useful for performing actions like logging or cleanup operations.
• RegisterPostEvictionCallback: A method to add a post-eviction callback directly to the cache entry options.
• ExpirationTokens: These allow the cache entry to be dependent on external triggers. For example, you can link a cache entry to a file using a FileChangeToken, and the cache entry will be evicted if the file changes.
AddExpirationToken(IChangeToken token): This method allows you to add a custom implementation of IChangeToken to trigger cache evictions. It's a powerful feature for creating complex cache invalidation logic based on external changes.
Example usage:
var cacheEntryOptions = new MemoryCacheEntryOptions(){ AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60), // Expires in 60 minutes SlidingExpiration = TimeSpan.FromMinutes(15), // Reset expiration to 15 minutes if accessed Priority = CacheItemPriority.High, // High priority Size = 1024 // Size of the entry is 1024 units}; cacheEntryOptions.RegisterPostEvictionCallback( (key, value, reason, state) => { Console.WriteLine($"Cache item {key} was removed due to {reason}."); }); _memoryCache.Set("MyCacheKey", myObject, cacheEntryOptions);
This was a junior and practical "tutorial" on how to use MemoryCache in a .NET application, which options exist, and what they are for.
It is certainly an excellent start for those who have not had experience with cashing.
IMemoryCache often does not find a real and practical role in real projects, especially if distributed caching is necessary.In those cases, I would recommend caching with Redis, which I will also talk about.
Write to me about what you would like me to write about when it comes to cashing.
You can see the full code for today's issue at the following GitHub repository.
That's all from me today.
For a more advanced caching strategy, check out HybridCache in ASP.NET Core and Dual-Key Redis Caching.
Stop arguing about code style. In this course you get a production-proven setup with analyzers, CI quality gates, and architecture tests — the exact system I use in real projects. Join here.
Not sure yet? Grab the free Starter Kit — a drop-in setup with the essentials from Module 01.
Design Patterns that Deliver — Solve real problems with 5 battle-tested patterns (Builder, Decorator, Strategy, Adapter, Mediator) using practical, real-world examples. Trusted by 650+ developers.
Just getting started? Design Patterns Simplified covers 10 essential patterns in a beginner-friendly, 30-page guide for just $9.95.
Every Monday morning, I share 1 actionable tip on C#, .NET & Architecture that you can use right away. Join here.
Join 20,000+ subscribers who mass-improve their .NET skills with actionable tips on C#, Architecture & Best Practices.
Subscribe to the TheCodeMan.net and be among the 20,000+ subscribers gaining practical tips and resources to enhance your .NET expertise.