In-memory caching is a fundamental technique for improving the performance and scalability of applications. In the .NET ecosystem, caching enables storing data in memory for fast and efficient access, avoiding costly operations such as database queries or external service calls.
What is In-Memory Caching?
In-memory caching is a mechanism that temporarily stores data to reduce access time for frequently used information. Instead of retrieving data from slower sources (such as a database or web service), the cache stores a local copy in memory, enabling faster and more efficient access. Caching is especially useful for read-heavy systems.
Benefits of In-Memory Caching
In-memory caching offers several advantages to enhance performance and scalability:
Reduced response time: Data is served quickly from memory, which is significantly faster than disk or network access.
Lower database load: Reduces the number of frequent database queries, minimizing the workload.
Improved scalability: With fewer external calls, the system can handle a larger number of concurrent users.
Types of Caching
Caching can be implemented in different ways, depending on the application’s needs:
Local cache: Stored in the memory of the running process. Example:
MemoryCachein .NET.Distributed cache: Stored on a separate cache server, accessible by multiple application instances. Examples include Redis and Memcached.
In this article, we’ll focus on local caching using MemoryCache.
Caching Considerations
Data consistency: Cached data might become stale. To avoid consistency issues, it’s crucial to implement proper expiration and invalidation policies. This includes setting a time-to-live (TTL) and ensuring data is updated or removed as needed.
Memory management: Excessive caching can lead to memory pressure, especially on resource-constrained systems. Monitor memory usage and apply strategies such as limiting cache size and evicting less-used items.
Security: Caches may store sensitive data, posing a security risk if not managed correctly. Ensure cached data is encrypted and access is restricted to authorized users and processes.
Implementing In-Memory Cache
To illustrate in-memory caching, let’s use an ASP.NET Core API project example. Initially, we have a controller that returns a list of products by querying the database every time:
[HttpGet]
public IActionResult Get()
{
return Ok(_productsRepository.GetAllProducts());
}
Assuming this product list is frequently requested and doesn’t change often, this is a suitable scenario for caching. By applying caching to this endpoint, we reduce database hits since most responses will come from memory.
Step 1 – Register the cache service
To use in-memory caching in ASP.NET, first register the service in the Program.cs (or Startup.cs) file. Add the following line after AddControllers():
builder.Services.AddMemoryCache();
Step 2 – Inject IMemoryCache in the Controller
Then inject an instance of IMemoryCache into the controller using dependency injection. Here’s the updated controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using CacheDemo.Models;
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
private readonly IMemoryCache _memoryCache;
private readonly ProductsRepository _productsRepository;
public ProductsController(IMemoryCache memoryCache, ProductsRepository productsRepository)
{
_memoryCache = memoryCache;
_productsRepository = productsRepository;
}
[HttpGet]
public IActionResult Get()
{
return Ok(_productsRepository.GetAllProducts());
}
}
As shown, IMemoryCache and the repository are injected into the constructor— a common pattern in ASP.NET projects.
Step 3 – Implement Cache Logic
Now update the Get() method to check the cache before querying the repository:
[HttpGet]
public IActionResult Get()
{
const string cacheKey = "products";
if (!_memoryCache.TryGetValue(cacheKey, out List products))
{
products = _productsRepository.GetAllProducts();
var cacheEntryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
};
_memoryCache.Set(cacheKey, products, cacheEntryOptions);
}
return Ok(products);
}
Explanation
Key Definition: We define a unique key (
"products") to identify the cached entry. This key will be used to retrieve, update, or remove data from the cache.TryGetValue: This method attempts to read the cached data. If the data exists, it’s returned immediately. Otherwise, the logic inside the
ifblock executes.Cache Miss Handling: When data isn’t in cache, we query the repository and store the result in the cache using
_memoryCache.Set(...).Expiration: The
MemoryCacheEntryOptionsis used to configure cache behavior. In this case,AbsoluteExpirationRelativeToNowensures the data expires 5 minutes after being cached, keeping the cache fresh.
For the next 5 minutes, repeated calls to this endpoint will return the data from memory instead of querying the database.
It’s important to emphasize that the cache expiration time is a key factor in the success of this strategy. In some cases, it’s also necessary to manually invalidate the cache when the source data changes to prevent serving outdated content.
Final Considerations
Efficient use of in-memory caching can significantly improve application performance. Choosing the right strategy based on your application’s requirements and monitoring memory usage is essential to avoid issues. With the tools and techniques discussed in this article, you can start applying caching in your .NET projects to achieve faster and more scalable applications.