Introduction

Caching is a technique that improves the performance and scalability of web applications by storing frequently accessed data in memory or other fast storage devices. Caching reduces the need to access the original data source, such as a database or an external service, which can be time-consuming and resource-intensive.

Caching is especially important for web applications that handle a large number of requests from multiple users. By caching data, web applications can respond faster to user requests and reduce the load on the server and the network.

In this article, we will explore how to use in-memory caching in ASP.NET Core. We will learn what in-memory caching is, how it works, how to set it up, and how to use it in our web applications.

What is Caching?

Caching is the process of storing data in a temporary storage location, called a cache, for faster access. A cache is usually a small and fast memory device that can store a subset of data from a larger and slower data source.

The idea behind caching is that data that is accessed frequently or recently is more likely to be accessed again in the near future. Therefore, by keeping such data in the cache, we can avoid accessing the original data source every time we need the data. This can improve the performance, scalability, and user experience of our web applications.

Benefits of caching

Some of the benefits of caching.

Types of caching

There are different types of caching that can be used for different purposes and scenarios. Some of the common types of caching.

When to use in-memory caching?

In-memory caching is one of the simplest and most effective ways to improve the performance and scalability of our web applications. However, it is not always appropriate or sufficient for every situation. We should use in-memory caching when:

What is in-memory caching?

In-memory caching is a type of caching that stores data in the memory of an ASP.NET Core application. ASP.NET Core provides built-in support for in-memory caching through the IMemoryCache interface and its default implementation MemoryCache.

The IMemoryCache interface exposes methods for creating, retrieving, updating, and removing cache entries. A cache entry consists of a key-value pair, where the key is a unique identifier for the cached data and the value is the actual data object.

The MemoryCache class implements IMemoryCache using a concurrent dictionary as its underlying storage mechanism. It also provides features such as cache expiration, eviction, priority, size limit, and dependency.

Advantages of in-memory caching

Some of the advantages of using in-memory caching in ASP.NET Core.

Limitations and considerations

Some of the limitations and considerations of using in-memory caching in ASP.NET Core.

Setting Up In-Memory Caching

Prerequisites

To use in-memory caching in ASP.NET Core, we need to have:

Adding the necessary NuGet packages

To install the Microsoft.Extensions.Caching.Memory NuGet package, we can use one of the following methods:

Configuring the caching service in Startup.cs

To use in-memory caching in ASP.NET Core, we need to register and configure the IMemoryCache service in the Startup.cs or Program.cs file(Depending on the structure of your application). We can do this by adding the following code to the ConfigureServices method:

// Add IMemoryCache service
services.AddMemoryCache(options =>
{
    // Set cache size limit (in bytes)
    options.SizeLimit = 1024 * 1024 * 100; // 100 MB

    // Set cache compaction percentage
    options.CompactionPercentage = 0.25; // 25%

    // Set cache expiration scan frequency
    options.ExpirationScanFrequency = TimeSpan.FromMinutes(5); // 5 minutes
});

The AddMemoryCache the method adds the default implementation IMemoryCache, which is MemoryCache, to the dependency injection container. It also registers a default set of options for MemoryCache. The MemoryCacheOptions class allows us to configure various aspects of MemoryCache, such as:

Cache expiration and eviction policies

One of the important aspects of caching is to determine when to remove or invalidate a cache entry. This can be done by using cache expiration and eviction policies.

The cache expiration policy defines how long a cache entry should remain valid in the cache. It can be based on absolute time (e.g., expire after 10 minutes) or sliding time (e.g., expire after 10 minutes of inactivity). A cache entry will be removed from the cache when it expires.

The cache eviction policy defines how to prioritize a cache entry for removal from the cache when the cache is full or under memory pressure. It can be based on priority (e.g., high, normal, low, never remove) or size (e.g., how much memory a cache entry occupies). A cache entry will be evicted from the cache when it has low priority or size.

ASP.NET Core allows us to specify the cache expiration and eviction policies for each cache entry by using the MemoryCacheEntryOptions class. We can create an instance of this class and pass it as a parameter when we create or update a cache entry. For example:

// Create an instance of MemoryCacheEntryOptions
var cacheEntryOptions = new MemoryCacheEntryOptions();

// Set absolute expiration policy (expire after 10 minutes)
cacheEntryOptions.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));

// Set sliding expiration policy (expire after 10 minutes of inactivity)
cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(10));

// Set priority policy (high priority)
cacheEntryOptions.Priority = CacheItemPriority.High;

// Set size policy (occupy 1 MB of memory)
cacheEntryOptions.Size = 1024 * 1024; // 1 MB

// Create or update a cache entry with the specified options
_cache.Set("key", "value", cacheEntryOptions);

The MemoryCacheEntryOptions class provides various methods and properties for configuring the cache expiration and eviction policies.

Storing and retrieving data from the cache

To store and retrieve data from the in-memory cache, we can use the methods provided by the IMemoryCache interface. The most commonly used methods are:

For example:

// Create or update a cache entry with key "message" and value "Hello, world!"
_cache.Set("message", "Hello, world!", cacheEntryOptions);

// Retrieve a cache entry with key "message"
var message = _cache.Get<string>("message"); // "Hello, world!"

// Try to retrieve a cache entry with key "message"
if (_cache.TryGetValue("message", out message))
{
    // Key exists in the cache
    Console.WriteLine(message); // "Hello, world!"
}
else
{
    // Key does not exist in the cache
    Console.WriteLine("Key not found");
}

// Remove a cache entry with key "message"
_cache.Remove("message");

Using caching with data retrieved from a database

One of the common scenarios where we can use caching is to store data that we retrieve from a database or an external service. This can reduce the number of database queries or service calls that we need to make and improve the performance and scalability of our web applications.

To use caching with data retrieved from a database, we can use one of the following patterns.

To implement these patterns in ASP.NET Core, we can use extension methods provided by the Microsoft.Extensions.Caching.Memory namespace.

For example:

// Using GetOrCreate to implement cache-aside pattern
var product = _cache.GetOrCreate("product_1", entry =>
{
    // Set cache entry options
    entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
    entry.Priority = CacheItemPriority.High;

    // Query product from database
    var product = _dbContext.Products.Find(1);

    // Return product as cache entry value
    return product;
});

// Using GetOrCreateAsync to implement cache-first pattern
var product = await _cache.GetOrCreateAsync("product_1", async entry =>
{
    // Set cache entry options
    entry.SetSlidingExpiration(TimeSpan.FromMinutes(10));
    entry.Priority = CacheItemPriority.Normal;

    // Query product from database asynchronously
    var product = await _dbContext.Products.FindAsync(1);

    // Return product as cache entry value
    return product;
});

// Using Set to implement write-through pattern
// Update product in database
var product = _dbContext.Products.Find(1);
product.Name = "New Name";
_dbContext.SaveChanges();

// Update product in cache
_cache.Set("product_1", product);

Handling cache misses

A cache miss occurs when we try to retrieve a cache entry with a key that does not exist in the cache.

When a cache miss occurs, we need to handle it gracefully and appropriately. Depending on our caching strategy and application logic, we can do one of the following actions.

// Return null if key does not exist in cache
var product = _cache.Get<Product>("product_1") ?? null;

// Return "Not Found" if key does not exist in cache
var message = _cache.Get<string>("message") ?? "Not Found";
// Throw an exception if key does not exist in cache
var product = _cache.Get<Product>("product_1") ?? throw new KeyNotFoundException("Product not found in cache");

// Log an error message if key does not exist in cache
var message = _cache.Get<string>("message");
if (message == null)
{
    _logger.LogError("Message not found in cache");
}

Conclusion

In this article, we have explored how to use in-memory caching in ASP.NET Core. We have learned what in-memory caching is, how it works, how to set it up, and how to use it in our web applications.

In-memory caching is a useful technique that can improve the performance and scalability of our web applications by storing frequently accessed data in memory. However, it also has some limitations and considerations that we need to be aware of and handle properly.

I hope you enjoyed this article and learned something new. Thank you for reading, and happy coding! 😊