X

Caching data by using in-memory cache in Asp.Net Core 3.1

Dung Do Tien Nov 13 2020 2434
In this article I will guide you how to cache data in Asp.net Core using in-memory cache. As you know, cache helps access and return data faster than many times if compared with getting data from the database. In Asp.net framework we have IIS cache (Http cache) but in Asp.net core microsoft replace it with In-memory cache technique.

Caching is a technique very important to help the web page load faster. It helps increase the UX (user experience) of the user. To cache data, I will introduce In-memory cache in Asp.net Core to help you cache very fast and easy.

1. What is In-memory cache in Asp.Net core?

When the first time a user requests to load a web page, to get data and respond to the browser, we have to connect to the database to get data. And then we will put this data to cache. In-memory cache will store data in the server's memory (RAM). From secondary request data will not get from the database, data will return from the server's cache. This helps data response faster.

The in-Memory cache can be used with:

- .NET Standard 2.0 or later.
- Any .NET implementation that targets .NET Standard 2.0 or later. For example, ASP.NET Core 2.0 or later.
- .NET Framework 4.5 or later.

2. What are the pros and cons of In-memory cache in Asp.Net core?

Pros

- It’s response quickly because data is stored in the server's memory (RAM).
- Highly reliable.
- It’s very suited for small and middle applications.

Cons

- Difficult to scale up. It’s suitable for a single server when we have many servers we can not share cache to all servers.
- It can take up all the memory of the server when traffic is large. 

3. Implement In-memory cache in Asp.Net core to caching data.

To implement in-memory cache in Asp.net core, Microsoft provided the IMemoryCache interface to help cache data to memory. Okay, now we will implement IMemoryCache step by step.

Step 1: Create ICacheBase interface to define some methods to help manipulate the cache.

public interface ICacheBase
{
    T Get<T>(string key);
    void Add<T>(T o, string key);
    void Remove(string key);
}

Step 2: Create CacheMemoryHelper class to implement ICacheBase interface.

- Firstly, we need to install the package  Microsoft.Extensions.Caching.Memory

You can install by Package Manager with the syntax below: 

Install-Package Microsoft.Extensions.Caching.Memory -Version 3.1.0

Or you can install by Nuget, you can select version before install, the version has to map with the version of Asp.net Core of your project.

And then we need to create  CacheMemoryHelper class and this class has a constructor and register DI(Dependency injection) to IMemoryCache interface.

public class CacheMemoryHelper : ICacheBase
{
    private IMemoryCache _cache;

    public CacheMemoryHelper (IMemoryCache cache)
    {
        this._cache = cache;
    }
}

To can disable or enable cache anytime you want, you can define a flag to do this.

public class CacheMemoryHelper : ICacheBase
{
    private bool IsEnableCache = false;
    private IMemoryCache _cache;

    public CacheMemoryHelper (IMemoryCache cache)
    {
        this._cache = cache;
        this.IsEnableCache = AppSettings.Instance.Get<bool>(“AppConfig:EnableCache”);
    }
}

Notice that : AppSettings.Instance.Get<bool>(“AppConfig:EnableCache”) this line code helps get value setting from appsettings.json file. If you don’t know how to read the key value from appsettings.json file you can read the article below:

Read configuration value from appsettings.json in ASP.Net Core

- Secondly, we will implement the Add() method to help add data to the memory.

public void Add<T>(T o, string key)
{
    if (IsEnableCache)
    {
        T cacheEntry;

        // Look for cache key.
        if (!_cache.TryGetValue(key, out cacheEntry))
        {
            // Key not in cache, so get data.
            cacheEntry = o;

            // Set cache options.
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                // Keep in cache for this time, reset time if accessed.
                .SetSlidingExpiration(TimeSpan.FromSeconds(7200)); // 2h

            // Save data in cache.
            _cache.Set(key, cacheEntry, cacheEntryOptions);
        }
    }
}

In that: 
- SetSlidingExpiration(): This method helps set expire time to cache, after expire time the data stored in the cache will be deleted and we have to get data from the database. 
- Set(): This method helps set data to the memory of the server. 

- Thirdly, implement the Get() method to help get data from memory and return that data to the end-user.

public T Get<T>(string key)
{
    return _cache.Get<T>(key);
}

- Fourthly and also finally, we need to implement the Remove method to help remove cache data by key.

public void Remove(string key)
{
    _cache.Remove(key);
}

Step 3: Execute cache for any business functions that want to cache data.

- Firstly, because cache stores memory by a pair key & value, so I will create a utility function help gen key for each function that wants to cache data.

using System.Security.Cryptography;
....
public class KeyCache
{
    public static string GenCacheKey(string cacheName, params object[] args)
    {
        if (args != null && args.Length > 0)
        {
            string separator =  "_";
            string cacheKey = cacheName;
            return args.Aggregate(cacheKey, (current, param) => current + (separator + (param.GetType() == typeof(string) ? CalculateMD5Hash(param.ToString()) : param)));
        }
        else return cacheName;
    }
    
    private static string CalculateMD5Hash(string input)
    {
        // step 1, calculate MD5 hash from input
        MD5 md5 = MD5.Create();
        byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
        byte[] hash = md5.ComputeHash(inputBytes);

        // step 2, convert byte array to hex string
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < hash.Length; i++)
        {
            sb.Append(hash[i].ToString("X2"));
        }
        return sb.ToString();
    }
}

- Secondly, for example, now I want to get the details of an article. I will create an IArticlesCached interface and ArticlesCached class implement as below: 

public interface IArticlesCached{
   Task<ArticleDetail> GetArticleDetail(int newsId);
}

public class ArticlesCached : IArticlesCached
{
    private ICacheBase _cache;
    private IArticlesBoFE _articlesBoFE;
    public ArticlesCached(ICacheBase cache , IArticlesBoFE articlesBoFE)
    {
        this._cache = cache;
        this._articlesBoFE = articlesBoFE;
    }

    public async Task<ArticleDetail> GetArticleDetail(int newsId)
    {
        try
        {
            var key = KeyCache.GenCacheKey("GetArticleDetail", newsId); // Generate key by function
            var articlesDetailBox = _cache.Get<ArticleDetail>(key); // Get data by key
            if (articlesDetailBox == null) // If data not exist from cache we will get it from database
            {
                articlesDetailBox = await _articlesBoFE.GetArticleDetail(newsId);
                _cache.Add<ArticleDetail>(articlesDetailBox, key); // Add data to cache
            }

            return articlesDetailBox;
        }
        catch (Exception ex)
        {
            return null;
        }
    }
}

Please see GetArticleDetail() method, in this method I have created a key, get data from the cache by key, if data does not exist I will get data from the database.

- Thirdly, we need config to disable & enable cache in appsettings.json file

....
"AppConfig": 
{
    "EnableCache": "true" // true or false to enable or disable cache 
}
....

- Finally, we need to register ICacheBase and some other interface to the DI container.

services.AddSingleton<ICacheBase, CacheMemoryHelper>();

services.AddSingleton<IArticlesCached, ArticlesCached>();
services.AddSingleton<IArticlesBoFE, ArticlesBoFE>();
services.AddSingleton<IArticlesDalFE, ArticlesDalFE>();

Well done, now we can get the details of an article with cache anywhere you want.

How do you clear MemoryCache in ASP.NET Core?

Because IMemoryCache of Asp. net core store in memory of server (DRAM/RAM) so to clear it you can do by some option as below:

  • Use Remove() function to remove cache by key, you can make and URL accept a key to delete any key cache you want.
  • Update the new code. if you use Docker you can update the new image.
  • Restart your server.
  • Listening to the HTTP request and check the header cache, if it contains a “refresh” key you will clear all cache.

4. Compare In-memory cache with non-cache in Asp.net Core

Currently, Quiz Dev blog is using the in-memory cache in Asp.net core 3.1 to cache data. I will test the performance of the Home page with cache and non-cache for you to see.

On the Home page of the Quiz Dev blog, we have 8 boxes approximately with 8 view components that have to get data from the database. And below is a short comparison about in-memory cache and non-cache you can refer:

Notice that: I only compare the result from the second time reload the page. It’s not the first.

You can see that getting data from memory is very fast. It only takes 28ms to load the page while if non-cache it takes 2680ms to load the page.

5. Summary

In this article, I only want to show you know why we need to use in-memory cache in Asp.Net Core 3.1. You can apply this cache technical for Asp.net core from version 2.0 to above.

You can download the full source code from GitHub here.