Optimizing Application Performance In-Memory Cache in .NET Core

Introduction

In the rapidly evolving world of software development, performance optimization is a critical aspect that can’t be overlooked. As applications grow more complex and data-intensive, developers are constantly seeking effective strategies to enhance their applications’ performance. One such strategy is leveraging the power of in-memory caching.

In this article, we will focus on .NET Core, a popular framework known for its efficiency and versatility. Specifically, we will delve into the use of In-Memory Cache in .NET Core, a powerful feature that can significantly boost your application’s performance.

In-memory caching works by storing data in the system’s main memory, reducing the need for time-consuming database operations. By keeping frequently accessed data in a cache, we can ensure that our application runs smoothly and efficiently, providing a seamless user experience.

Whether you’re a seasoned .NET developer or just starting your journey, this article will provide you with a comprehensive understanding of in-memory caching in .NET Core and how to use it to optimize your application’s performance. So, let’s get started!

IMemoryCache is an interface provided by .NET Core that represents a cache stored in the memory of the web server. It’s a part of the Microsoft.Extensions.Caching.Memory namespace. Here’s what it does and the benefits of using it:

What In-Memory Cache Do?

IMemoryCache allows you to store data in the memory of your application. This data could be anything that’s expensive or time-consuming to generate or fetch, such as data from a database query, complex calculations, or API responses. The stored data can be retrieved using a unique key.

Benefits of Using IMemoryCache

  1. Performance Improvement: Fetching data from memory is significantly faster than fetching it from a database or an external API. By storing frequently accessed data in memory, you can reduce the load on your database or external services and speed up your application.
  2. Scalability: By reducing the dependency on external resources, you make your application more scalable. It can handle more users without slowing down or needing more database connections.
  3. Flexibility: IMemoryCache provides a lot of flexibility. You can specify options for each cache entry, such as absolute or sliding expiration times. This allows you to control how long the data stays in the cache based on your application’s needs.
  4. Efficiency: It allows efficient use of system resources by holding onto expensive-to-create items while they are still being used and discarding them if they’re not needed.

Now let's see how to implement In-Memory caching with a practical example in .NET Core:

1. Install Required NuGet Packages

You need to install the necessary NuGet packages for your project.  You can install it using the following command in the Package Manager Console:

Install-Package Microsoft.Extensions.Caching.Memory

2. Initialize Memory Cache

You need to initialize the memory cache in your application. This can be done in the Startup.cs file of your API. In the ConfigureServices method, add the following line:

services.AddMemoryCache();

3. Create ContainerConfiguration Class

In our demo application, we have a create static class named `ContainerConfiguration`. This class is responsible for setting up the services that our application will use.

public static class ContainerConfiguration
{
    public static IServiceProvider Configure()
    {
        var services = new ServiceCollection();
        services.AddMemoryCache();
        services.AddScoped<IInMemoryCacheService, InMemoryCacheService>();

        return services.BuildServiceProvider();
    }
}

In the Configure() method, we first create a new ServiceCollection. This is a collection of service descriptors, which we can use to specify the services our application requires.

Next, we call services.AddMemoryCache(). This is an extension method provided by .NET Core that adds services required for in-memory caching to our ServiceCollection.

After setting up in-memory caching, we add our own service to the collection. We do this by calling services.AddScoped<IInMemoryCacheService, InMemoryCacheService>(). This tells .NET Core that whenever an IInMemoryCacheService is requested, it should provide an instance of InMemoryCacheService. The AddScoped method means that a new instance  InMemoryCacheService will be created once per request.

Finally, we call services.BuildServiceProvider(). This method builds  ServiceCollection into an IServiceProvider that we can use to retrieve the services our application needs.

4. Create Cache Service Class

We have a service class named InMemoryCacheService that implements the IInMemoryCacheService interface. This class is a crucial part of our application as it provides methods to interact with the in-memory cache.

public interface IInMemoryCacheService
{
    /// <summary>
    /// Retrieves the value associated with the given key from the cache.
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    T Get<T>(string key);

    /// <summary>
    /// Stores the given value in the cache associated with the specified key.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="expirationTime"></param>
    void AddOrUpdate<T>(string key, T value, TimeSpan expirationTime);

    /// <summary>
    /// AddOrUpdate
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="value"></param>
    void AddOrUpdate<T>(string key, T value);

    /// <summary>
    /// Removes the value associated with the specified key from the cache. 
    /// </summary>
    /// <param name="key"></param>
    void Delete(string key);

    /// <summary>
    /// Checks if the specified key exists in the cache.
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    bool IsExists<T>(string key);
}
 public class InMemoryCacheService : IInMemoryCacheService
 {
     /// <summary>
     /// MemoryCache
     /// </summary>
     private readonly IMemoryCache _memoryCache;

     /// <summary>
     /// MemoryCacheService
     /// </summary>
     /// <param name="memoryCache"></param>
     public InMemoryCacheService(IMemoryCache memoryCache)
     {
         _memoryCache = memoryCache;
     }

     /// <summary>
     /// Retrieves the value associated with the given key from the cache.
     /// </summary>
     /// <param name="key"></param>
     /// <returns></returns>
     public T Get<T>(string key)
     {
         return _memoryCache.Get<T>(key);
     }

     /// <summary>
     /// Stores the given value in the cache associated with the specified key.
     /// </summary>
     /// <param name="key"></param>
     /// <param name="value"></param>
     /// <param name="expirationTime"></param>
     public void AddOrUpdate<T>(string key, T value, TimeSpan expirationTime)
     {
         var cacheEntryOptions = new MemoryCacheEntryOptions()
             .SetAbsoluteExpiration(expirationTime);

         _memoryCache.Set(key, value, cacheEntryOptions);
     }

     /// <summary>
     /// Stores the given value in the cache associated with the specified key.
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="key"></param>
     /// <param name="value"></param>
     public void AddOrUpdate<T>(string key, T value)
     {
         var cacheEntryOptions = new MemoryCacheEntryOptions()
             .SetPriority(CacheItemPriority.NeverRemove);

         _memoryCache.Set(key, value, cacheEntryOptions);
     }

     /// <summary>
     /// Removes the value associated with the specified key from the cache. 
     /// </summary>
     /// <param name="key"></param>
     public void Delete(string key)
     {
         _memoryCache.Remove(key);
     }

     /// <summary>
     /// Checks if the specified key exists in the cache.
     /// </summary>
     /// <param name="key"></param>
     /// <returns></returns>
     public bool IsExists<T>(string key)
     {
         return _memoryCache.TryGetValue<T>(key, out _);
     }
 }

The InMemoryCacheService class has a private field _memoryCache of type IMemoryCache, which represents the in-memory cache of our application. This field is initialized in the constructor of the class.

The class provides several methods to interact with the cache:

  • Get<T>(string key): This method retrieves an item from the cache using a key.
  • AddOrUpdate<T>(string key, T value, TimeSpan expirationTime): This method adds a new item to the cache or updates an existing item. The item will be removed from the cache after the specified expirationTime.
  • AddOrUpdate<T>(string key, T value): This method adds a new item to the cache or updates an existing item. The item will not be removed from the cache unless explicitly deleted.
  • Delete(string key): This method removes an item from the cache using a key.
  • IsExists<T>(string key): This method checks if an item exists in the cache using a key.

5. Use service and Perform Cache Operation

We will use the above service and perform different cache operations in our application. 

public class Program
{
    static void Main(string[] args)
    {
        var provider = ContainerConfiguration.Configure();

        var _cacheService = provider.GetService<IInMemoryCacheService>();
        // Add or update an item in the cache with an expiration time
        _cacheService.AddOrUpdate("Key1", "Value1", TimeSpan.FromMinutes(5));
        Console.WriteLine("Added 'Key1' with value 'Value1' to the cache with an expiration time of 5 minutes.");

        // Add or update an item in the cache without an expiration time
        _cacheService.AddOrUpdate("Key2", "Value2");
        Console.WriteLine("Added 'Key2' with value 'Value2' to the cache without an expiration time.");

        // Get an item from the cache
        var value1 = _cacheService.Get<string>("Key1");
        var value2 = _cacheService.Get<string>("Key2");
        Console.WriteLine($"Retrieved value '{value1}' for 'Key1' and value '{value2}' for 'Key2' from the cache.");

        // Check if an item exists in the cache
        bool exists1 = _cacheService.IsExists<string>("Key1");
        bool exists2 = _cacheService.IsExists<string>("Key2");
        Console.WriteLine($"Checked existence of 'Key1' and 'Key2' in the cache. Existence results: Key1 - {exists1}, Key2 - {exists2}");

        // Delete an item from the cache
        _cacheService.Delete("Key1");
        Console.WriteLine("Deleted 'Key1' from the cache.");

        // Check again if the item exists in the cache
        bool existsAfterDelete = _cacheService.IsExists<string>("Key1");
        Console.WriteLine($"Checked existence of 'Key1' in the cache after deletion. Existence result: {existsAfterDelete}");
        Console.ReadLine();
    }
}

As a result, we will get below output in the application.

In-Memory Cache in .NET Core

Disadvantages of In-Memory Cache in ASP.NET Core

While In-Memory Cache in ASP.NET Core provides significant performance benefits, it’s important to be aware of its potential disadvantages:

  1. Resource Consumption: If configured incorrectly, in-memory caching can consume a significant amount of your server’s resources1.
  2. Cost: With the scaling of the application and longer caching periods, maintaining the server can prove to be costly1.
  3. Cloud Deployment: If deployed in the cloud, maintaining consistent caches can be difficult1.
  4. No Persistence or Backup: In-memory caching does not persist or backup the cached data, as it is stored in the volatile memory of an application. It can be lost or cleared when the application restarts, shuts down, or crashes2.
  5. Scalability: While in-memory caches are fast, they can be difficult to scale. Because the data is stored in memory, you’re limited by the amount of memory available on your machine3.
  6. Complexity: Implementing caching can add complexity to your application. You need to manage when and how data is added to the cache, how long it stays in the cache, and when it is removed3.

Conclusion

In-memory caching is a powerful technique that can significantly improve the performance of your applications by reducing the need for expensive database calls. By storing frequently accessed data in memory, you can ensure that your application runs smoothly and efficiently.

In this article, we explored the concept of in-memory caching and how it can be implemented in .NET Core using the IMemoryCache interface. We also walked through a demo application that uses in-memory caching to manage data.

We delved into the code of the demo application, explaining how the InMemoryCacheService class interacts with the in-memory cache. We also demonstrated how to use the InMemoryCacheService class in a sample usage scenario.

Understanding and effectively utilizing in-memory caching can be a game-changer for your applications. It’s a tool that, when used correctly, can make your applications faster and more responsive, providing a better experience for your users.

In the attached code, you’ll find a practical example of how in-memory caching can be implemented in a .NET Core application. Feel free to explore it for a deeper understanding of the concepts discussed in this article.

I hope you will find this article helpful. If you have any suggestions, then please feel free to ask in the comment section.

Thank you.


Similar Articles