In-Memory Caching in .NET

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: MemoryCache in .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<Product> products))
    {
        products = _productsRepository.GetAllProducts();

        var cacheEntryOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
        };

        _memoryCache.Set(cacheKey, products, cacheEntryOptions);
    }

    return Ok(products);
}

				
			

Explanation

  1. 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.

  2. TryGetValue: This method attempts to read the cached data. If the data exists, it’s returned immediately. Otherwise, the logic inside the if block executes.

  3. Cache Miss Handling: When data isn’t in cache, we query the repository and store the result in the cache using _memoryCache.Set(...).

  4. Expiration: The MemoryCacheEntryOptions is used to configure cache behavior. In this case, AbsoluteExpirationRelativeToNow ensures 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.