How To Implement Caching In The .NET Core Web API Application

Introduction

Performance is one of the important factors of the application. There are many factors (such as DB call (querying), Web service load, etc) that impact the application performance. We have to more concentrate on the performance side to deliver quality applications. For that, one of the options is called cache. In this article, we are going to explore what cache is and how to implement caching in the .NET Core web API application.

What is Cache?

Caching is the technique of storing the data which are frequently used. Those data can be served faster for any future or subsequent requests by eliminating unnecessary requests to external data sources.

Why do we need a cache mechanism?

Sometimes our application frequently calls the same method and fetches the data from the database. The output of these requests is the same at all times. It doesn't get changed or updated in the database. In this case, we can use caching to reduce the database calls and retrieve the data directly from the cache memory.

Cache Mechanism

There are 3 types of cache available

  1. In-Memory Cache: The data are cached in the server's memory.
  2. Persistent in-process Cache: The data are cached in some file or database.
  3. Distributed Cache: The data are cached in a shared cache and multiple processes. Example: Redis cache

In this article, we are mainly focusing on the In-Memory Cache mechanism.

In-Memory Cache

In-memory cache means storing the cache data on the server's memory.

Advantages of In-Memory Cache

  • It is easier and quicker than other caching mechanisms
  • Reduce load on Web Services/ Database
  • Increase Performance
  • Highly Reliable
  • It is suited for small and middle applications.

Disadvantages of In-Memory Cache

  • If the cache is not configured properly, then it can consume the server’s resources.
  • Increased Maintenance.
  • Scalability Issues. It is suitable for a single server. If we have many servers, then we can't share the cache to all servers.

How to implement In-Memory cache in the ASP.NET Core Web API application

Prerequisites

  • Visual Studio 2019 or Visual Studio 2022

Follow the below steps to create the ASP.NET Web API using Visual Studio 2019.

Step 1. Open Visual Studio 2019, and click Create a New Project.

VscodeNewProject

Step 2. Select the ASP.NET Core Web Application project template and click Next.

ConsoleApp

Step 3. Enter the project name as Sample_Cache and Click Next.

Step 4. Select .NET Core 3.1 and Empty project and Click Create.

EmptyConsoleApp

Step 5. Install the Microsoft.Extensions.Caching.Memory NuGet Package to implement the in-memory cache.

MicrosoftCschingExtensionls

Step 6. Create the EmployeeController class and inject the in-memory caching service into the constructor.

public class EmployeeController: ControllerBase {
    private ICacheProvider _cacheProvider;
    public EmployeeController(ICacheProvider cacheProvider) {
        _cacheProvider = cacheProvider;
    }
}

Step 7. Register services.AddMemoryCache() to ConfigureServices() method in the Startup class. 

public void ConfigureServices(IServiceCollection services)
{
	services.AddMemoryCache();
}

Simple Cache Implementation

Let us add the below code in the EmployeeController class.

EmployeeController.cs

[Route("getAllEmployee")]
public IActionResult GetAllEmployee() {
    if (!_cache.TryGetValue(CacheKeys.Employees, out List < Employee > employees)) {
        employees = GetEmployeesDeatilsFromDB(); // Get the data from database
        var cacheEntryOptions = new MemoryCacheEntryOptions {
            AbsoluteExpiration = DateTime.Now.AddMinutes(5),
                SlidingExpiration = TimeSpan.FromMinutes(2),
                Size = 1024,
        };
        _cache.Set(CacheKeys.Employees, employees, cacheEntryOptions);
    }
    return Ok(employees);
}

 Employee.cs

public class Employee
{
	public int Id { get; set; }
	[Required]
	public string FirstName { get; set; }
	[Required]
	public string LastName { get; set; }
	[Required]
	public string EmailId { get; set; }
}

CacheKeys.cs

public static class CacheKeys
{
	public static string Employees => "_Employees";
}

In the above code, whenever the request is coming, first check whether the cache has value (employee details) or not. If not, then employees' details are retrieved from the database and stored in the cache. Since the cache has employee details, for the next request, the employee details are fetched from the cache instead of the database.

In-Memory Cache Parameters

  • SizeThis allows you to: Set the size of this particular cache entry so that it doesn’t start consuming the server resources.
  • SlidingExpiration: How long the Cache will be inactive? A cache entry will expire if it is not used by anyone for this particular time period. In our case, we have set SlidingExpiration is 2 minutes. If there are no requests for this cache entry for 2 minutes, then the cache will be deleted.
  • AbsoluteExpiration: Actual expiration time for cached Value. Once the time is reached, then the cache entry will be removed. In our case, we have set AbsoluteExpiration is 5 minutes. The cache will expire once the time is reached 5 minutes. Absolute expiration should not be less than the Sliding Expiration.

Output

First-time Request 

PostmanTEsting

Second Request

POstman Response

Problems

If there are multiple requests at the same time for the same item, then the requests won't wait for the first one to be complete. Since there are no values in the cache, the database calls happen to all requests until the cache has been set. 

For example. 

  1. User 'A' sends a request for the employee details. Here cache doesn't have any values. So, this request calls the database to retrieve the employee details. Let’s consider that getting employee details from the database takes 5 seconds. 
  2. Meanwhile (Before completing the User 'A' request), User 'B' sends a request for the same employee details. Here the cache doesn't have any value. So, this request also has a database call.

Cache Implementation using lock

We are going to use the lock to solve the above problem.

For example, 

  1. User 'A' sends the request for the employee details. Here cache doesn't have any values. So, it enters into the lock state and makes database calls for retrieving the employee details.
  2. Meanwhile, User 'B' sends the request for the employee details. This request will wait until the lock is released. This will reduce the database call for the same data.
  3. Once employee details are retrieved from the database, it's stored in the cache. Now, the lock is released and sends a response to User 'A'.
  4. Now, the values are fetched from the cache and sent a response to User 'B'.

We are going to refactor the cache mechanism using locks. Let's consider the below code.

EmployeeController.cs

[Route("api/[controller]")]
public class EmployeeController: ControllerBase {
    private ICacheProvider _cacheProvider;
    public EmployeeController(ICacheProvider cacheProvider) {
            _cacheProvider = cacheProvider;
        }
        [Route("getAllEmployee")]
    public IActionResult GetAllEmployee() {
        try {
            var employees = _cacheProvider.GetCachedResponse().Result;
            return Ok(employees);
        } catch (Exception ex) {
            return new ContentResult() {
                StatusCode = 500,
                    Content = "{ \n error : " + ex.Message + "}",
                    ContentType = "application/json"
            };
        }
    }
}

CacheProvider.cs

public class CacheProvider: ICacheProvider {
    private static readonly SemaphoreSlim GetUsersSemaphore = new SemaphoreSlim(1, 1);
    private readonly IMemoryCache _cache;
    public CacheProvider(IMemoryCache memoryCache) {
        _cache = memoryCache;
    }
    public async Task < IEnumerable < Employee >> GetCachedResponse() {
        try {
            return await GetCachedResponse(CacheKeys.Employees, GetUsersSemaphore);
        } catch {
            throw;
        }
    }
    private async Task < IEnumerable < Employee >> GetCachedResponse(string cacheKey, SemaphoreSlim semaphore) {
        bool isAvaiable = _cache.TryGetValue(cacheKey, out List < Employee > employees);
        if (isAvaiable) return employees;
        try {
            await semaphore.WaitAsync();
            isAvaiable = _cache.TryGetValue(cacheKey, out employees);
            if (isAvaiable) return employees;
            employees = EmployeeService.GetEmployeesDeatilsFromDB();
            var cacheEntryOptions = new MemoryCacheEntryOptions {
                AbsoluteExpiration = DateTime.Now.AddMinutes(5),
                    SlidingExpiration = TimeSpan.FromMinutes(2),
                    Size = 1024,
            };
            _cache.Set(cacheKey, employees, cacheEntryOptions);
        } catch {
            throw;
        } finally {
            semaphore.Release();
        }
        return employees;
    }
}

Explanation of the code

First, we are checking if the cache has value or not.

bool isAvailable = _cache.TryGetValue(cacheKey, out List<Employee> employees);

if (isAvailable)
	return employees;

If the value is available in the cache, then get the value from the cache and send a response to the user else, then asynchronously wait to enter the Semaphore.

await semaphore.WaitAsync();

Once a thread has been granted access to the Semaphore, recheck if the value has been populated previously for safety (Avoid concurrent thread access).

isAvailable = _cache.TryGetValue(cacheKey, out employees);

if (isAvailable)
	return employees;

Still doesn’t have a value in the cache, then call the database and store the value in the cache.

employees = EmployeeService.GetEmployeesDeatilsFromDB();

var cacheEntryOptions = new MemoryCacheEntryOptions
{
	AbsoluteExpiration = DateTime.Now.AddMinutes(5),
	SlidingExpiration = TimeSpan.FromMinutes(2),
	Size = 1024,
};

_cache.Set(cacheKey, employees, cacheEntryOptions);

Send a response to the user.

Summary

In this article, we have learned the following functionality using .NET Core Web API. 

  • What is Caching, 
  • Types of Caching mechanism
  • Detail about In-Memory Caching
  • Implementing In-Memory Caching in ASP.NET Core Web API. 


Similar Articles