LinkedIn
GitHub profile
Проекты
Advertisement
RSS
.NET Framework .NET C# VB.NET LINQ ASP.NET Web API REST SignalR Windows Forms WPF WCF RabbitMQ PHP SQL Server MySQL PostgreSQL MariaDB SQLite MongoDB ADO.NET ORM Entity Framework Dapper XML JSON HTML5 CSS3 Bootstrap JavaScript jQuery Angular React TypeScript NPM Blazor UI/UX Responsive Web Design Redis Elasticsearch GraphQL Grafana Agile Scrum Kanban Windows Server IIS PowerShell Active Directory TFS Azure Automation Software Reverse Engineering Performance Optimization Git Jira/Confluence CI/CD TeamCity SOLID KISS DRY YAGNI
Always will be ready notify the world about expectations as easy as possible: job change page

Simple In-Memory Caching in .Net Core with IMemoryCache

Created: Feb 7, 2021
Author: Sahan Serasinghe
Source: https://sahansera.dev/in-memory-caching-aspcore-dotnet/
Views: 207

Caching is the process of storing the data that’s frequently used so that data can be served faster for any future requests. Suppose we have a very lightweight process which talks to another server whose data is not going to change frequently; “Our service” and “Users Service” (which returns an array of users) respectively.

Without any caching in place, we would be making multiple requests which will ultimately result in timeouts or making the remote server unnecessarily busy.

Let’s have a look at how we can improve the performance of these requests by using a simple caching implementation. .NET Core provides 2 cache implementations under Microsoft.Extensions.Caching.Memory library:

  1. IMemoryCache - Simplest form of cache which utilises the memory of the web server.
  2. IDistributedCache - Usually used if you have a server farm with multiple app servers.

In this example we will be using the IMemoryCache along with latest version of .NET Core as of yet, which is version 3.1.

These are the steps we are going to follow:

  1. Create/Clone a sample .NET Core app.
  2. Naive implementation.
  3. Refactoring our code to use locking.

1. Create/Clone a sample .NET Core app

You can simply clone In-memory cache sample code repo I have made for the post. If not, make sure that you scaffold a new ASP.NET Core MVC app to follow along.

First, we need to inject the in-memory caching service into the constructor of the HomeController.

public class HomeController : Controller
{
    private readonly IMemoryCache _cache;

    public HomeController(ILogger<HomeController> logger, IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }
}

Note: With .Net Core 3.1 you don’t need to specifically register the memory caching service. However, if you are using a prior version such as 2.1, then you will need to add the following line in the Startups.cs:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMemoryCache(); // Add this line

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
}

2. Naive implementation

For the sake of this tutorial, we’ll use a free external API such as reqres.in/api/users. Let’s imagine we want to cache the response of the API. For simplicity I have used the example code provided by Microsoft Docs.

// Code removed for brevity
...

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

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

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

Explanation

The code is pretty straightforward. We first check whether we have the value for the given key present in our in-memory cache store. If not, we do the request to get the data and store in our cache. What SetSlidingExpiration does is that as long as no one accesses the cache value, it will eventually get deleted after 10 seconds. But if someone accesses it, the expiration will get renewed.

Here’s my implementation with a bit of code re-structuring:

var users = _cacheProvider.GetFromCache<IEnumerable<User>>(cacheKey);
if (users != null) return users;

// Key not in cache, so get data.
users = await func();

var cacheEntryOptions = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(10));
_cacheProvider.SetCache(cacheKey, users, cacheEntryOptions);

return users;

await func() will wait and return the response from our external API endpoint (provided that it’s passed into our method) so that we can use that value to store in our cache.

Have a look at the full implementation of my GetCachedResponse() in CachedUserService.cs for a more generic solution to handle any type of data.

This gets the job done for a very simple workload. But how can we make this more reliable if there are multiple threads accessing our cache store? Let’s have a look in our next step.

3. Refactoring our code to use locking

Now, let’s assume that we have several users accessing our service which means there could be multiple accessing our in-memory cache. One way to make sure that no two different users get different results is by utilising .Net locks. Refer the following scenario:

Let’s breakdown the sequence of requests and responses:

  1. User A makes a request to our web service.
  2. In-memory cache doesn’t have a value in place, it enters in to lock state and makes a request to the Users Service.
  3. User B makes a request to our web service and waits till the lock is released.
  4. This way, we can reduce the number of calls being made to the external web service. returns the response to our web service and the value is cached.
  5. Lock is released, User A gets the response.
  6. User B enters the lock and the cache provides the value (as long it’s not expired).
  7. User B gets the response.

The above depiction is a very high-level abstraction over all the awesome stuff that happens under the covers. Please use this as a guide only. Let’s implement this!

...
var users = _cacheProvider.GetFromCache<IEnumerable<User>>(cacheKey);

if (users != null) return users;
try
{
    await semaphore.WaitAsync();

    // Recheck to make sure it didn't populate before entering semaphore
    users = _cacheProvider.GetFromCache<IEnumerable<User>>(cacheKey);

    if (users != null) return users;
    users = await func();
    
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(10));

    _cacheProvider.SetCache(cacheKey, users, cacheEntryOptions);
}
finally
{
    // It's important to do this, otherwise we'll be locked forever
    semaphore.Release();
}

return users;

Explanation

Same as in our previous example we first check our cache for the presence of the value for a key provided. if not, we then asynchronously wait to enter the Semaphore. Once our thread has been granted access to the Semaphore, we recheck if the value has been populated previously for safety. If we still don’t have a value, we then call our external service and store the value in the cache.

Have a look at the CachedUserService.cs for the full implementation.

Hope you enjoyed this tutorial. Happy to know your thoughts! 🙂

References

  1. https://www.blexin.com/en-US/Article/Blog/In-memory-caching-in-ASPNET-Core-45
  2. https://www.infoworld.com/article/3230129/how-to-use-in-memory-caching-in-aspnet-core.html
  3. https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/
Similar
Nov 7, 2020
Author: Rebai Hamida
IntroductionDuring this article, we will learn how to work with the microservice architecture patterns and Docker containers using the .NET Core 3 platform to build a distributed system.Monolithic vs Microservices ArchitectureBefore talking about Microservices, we need to understand the difference...
17 декабря 2014 г.
Краткий обзор .NET Core, как это соотносится с .NET Framework и что это все означает для кросс-платформенной разработки и разработки с открытым кодом.Взгляд назад – мотивация для .NET CoreПрежде всего, давайте оглянемся назад, чтобы понять, как платформа .NET была устроена...
Nov 22, 2021
Author: MBARK T3STO
Dispose and Finalize are two methods you often use to release resources occupied by your .NET and .NET Core applications running in the context of the CLR. Most importantly, if you have unmanaged resources in your application, you should release...
Apr 24, 2022
Author: Juan Alberto España Garcia
The security of .NET applications is necessary and knowing how to protect it is not always an easy task. As developers, we must be clear that an insecure application can be a serious problem: from modifying the operation of the...
Send message
Email
Your name
*Message


© 1999–2023 WebDynamics
1980–... Sergey Drozdov
Area of interests: .NET | .NET Core | C# | ASP.NET | Windows Forms | WPF | Windows Phone | HTML5 | CSS3 | jQuery | AJAX | MS SQL Server | Transact-SQL | ADO.NET | Entity Framework | IIS | OOP | OOA | OOD | WCF | WPF | MSMQ | MVC | MVP | MVVM | Design Patterns | Enterprise Architecture | Scrum | Kanban