Distributed Caching in ASP.NET Core with Redis
Tags: dotnet, csharp, tutorial
Published: 2021-03-01 11:38:23

---
title: Distributed Caching in ASP.NET Core with Redis
published: true
date: 2021-03-01 11:38:00 UTC
tags: #dotnet #csharp #tutorial
cover_image: https://sahansera.dev/static/8d28c5eae05b19aaf2fac0b63ec6a136/fdaa2/distributed-caching-in-aspdotnet-core-with-redis.png
canonical_url: https://sahansera.dev/distributed-caching-aspnet-core-redis/
---

About a year ago, I wrote a blog post on [simple In-Memory Caching in ASP.NET Core with IMemoryCache](https://sahansera.dev/in-memory-caching-aspcore-dotnet/). This article mainly introduced the concept of caching and how we can store stuff in the server’s memory for simple tasks. Today’s objective is to leverage the IDistributedCache to do some distributed caching so that we can horizontally scale out our web app.

For this specific tutorial, I will be using Redis as my cache provider. Redis is a battle-tested, fast memory cache that can store many types of objects. Redis is being used by giants such as Twitter, Github, Instagram, Stackoverflow, Medium, Airbnb etc.

> 💡 You can find the accompanying code for this blog post from [here](https://github.com/sahan91/DistributedCacheAspNetCoreRedis).

Here’s a snapshot of what we are going to be building.

![distributed-caching-in-aspdotnet-core-with-redis-1.png](https://sahansera.dev/static/f5cf079e725b11c30eb666b3ff414626/5a190/distributed-caching-in-aspdotnet-core-with-redis-1.png "distributed-caching-in-aspdotnet-core-with-redis-1.png")

1. User requests a `user` object.
2. App server checks if we already have a user in the cache and return the object if present.
3. App server makes an HTTP call to retrieve the list of users.
4. Users service returns the users list to the app server.
5. App server sends the users list to the distributed (Redis) cache.
6. App server gets the cached version until it expires (TTL).
7. User gets the cached user object.

The main reason why we call this a distributed cache is that it lives outside of our application server (as opposed to traditional in-memory caching) and we have the flexibility of scaling it horizontally (when operating in the cloud), if need be. Head over [here](https://azure.microsoft.com/en-us/services/cache/#what-you-can-build) to have a look at how this could be useful in enterprise applications.

The `IDistributedCache` interface provides us with a bunch of methods to manipulate your cache. And the actual implementation is specific to the technology we want to use. Here’s a summary of different ways you can do this.

| Technology | NuGet package | Notes |
| --- | --- | --- |
| Distributed Memory Cache | - | This is only recommended for dev and testing purposes. This is not an actual distributed cache. |
| Distributed SQL Server Cache | Microsoft.Extensions.Caching.SqlServer | Use SQL Server instance as a cache (locally or in cloud with Azure SQL Server). |
| Distributed Redis Cache | Microsoft.Extensions.Caching.StackExchangeRedis | Use Redis as a backing store (locally or in cloud with Azure Redis Cache)client package is Developed by peeps at StackExchange. |
| Distributed NCache Cache | NCache.Microsoft.Extensions.Caching.OpenSource | Wrapper around the NCache Distributed Cache |

### Scaffolding a sample app

We will create a Web MVC app in [ASP.NET](http://asp.NET) Core 5.

```bash
dotnet new mvc -n DistributedCache
dotnet new sln
dotnet sln add DistributedCache
```

Let’s go ahead and add the Redis client package from NuGet.

```bash
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis --version 5.0.1
```

### Creating a Redis docker container

For this step, I assume that you have already installed Docker on your machine. It’s handy to have this so that you can spin up your own Redis container whenever you want for development purposes.

```bash
docker run --name redis-cache -p 5002:6379 -d redis
```

We are telling docker to use the official `redis` image and spin up a container with the name `redis-cache` and bind port `6379` of the container to the port `5002` of your host machine. Why I chose port 5002 is that it might be open as it’s a less obvious port number.

If you haven’t got the Redis image locally, it will fetch that from the DockerHub and spin up a new container under the name `redis-cache`. Next let’s verify that our docker instance is up and running. You could do so with,

```bash
docker ps -a
```

or alternatively with `docker ps -a | grep redis-cache` to filter our the output, if you have a bunch of containers running in the background like I do 😅

![distributed-caching-in-aspdotnet-core-with-redis-2.png](https://sahansera.dev/static/4c45c34c2393e7a219d86c62c9d807c4/5a190/distributed-caching-in-aspdotnet-core-with-redis-2.png "distributed-caching-in-aspdotnet-core-with-redis-2.png")

Now that we have the Redis container up and running let’s configure our web app to use it.

### Application Configuration

[Startup.cs](https://github.com/sahan91/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/Startup.cs)

Since we have already added the required NuGet package, we only need to register its service in our app’s DI container and tell it where to find our Redis instance.

```csharp
// Register the RedisCache service
services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = Configuration.GetSection("Redis")["ConnectionString"];
});
```

When we call `AddStackExchangeRedisCache` on the services object, it registers a singleton of RedisCache class against the IDistributedCache interface under the covers. This is what it looks like in the [source](https://github.com/dotnet/aspnetcore/blob/main/src/Caching/StackExchangeRedis/src/StackExchangeRedisCacheServiceCollectionExtensions.cs):

```csharp
// ...
services.Add(ServiceDescriptor.Singleton<IDistributedCache, RedisCache>());
// ..
```

[appsettings.json](https://github.com/sahan91/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/appsettings.json)

Since we have the Docker instance up and running at port `5002`, we can mention that for development settings.

```csharp
"Redis": {
  "ConnectionString": "localhost:5002"
}
```

I have brought across the service from my previous tutorial and added them to this project. You can find them under the `Services` folder. In fact, I have made the code to look more bit simpler as well.

### Implementation

The functionality is pretty simple, and here’s what we going to do:

1. Get the cached user (if any) and display its email address
2. A button to invoke a HTTP call and cache a list of users
3. A button to clear the cache

The UI would look something like the following.

![distributed-caching-in-aspdotnet-core-with-redis-3.png](https://sahansera.dev/static/7cf059b76040187a7ae21a9ac5b895e2/5a190/distributed-caching-in-aspdotnet-core-with-redis-3.png "distributed-caching-in-aspdotnet-core-with-redis-3.png")

Let’s look at the main entry point of the actions, the HomeController class.

[HomeController.cs](https://github.com/sahan91/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/Controllers/HomeController.cs)

```csharp
public async Task<IActionResult> Index()
{
    var users = (await _cacheService.GetCachedUser())?.FirstOrDefault();
    return View(users);
}

public async Task<IActionResult> CacheUserAsync()
{
    var users = await _usersService.GetUsersAsync();
    var cacheEntry = users.First();
    return View(nameof(Index), cacheEntry);
}

public IActionResult CacheRemoveAsync()
{
    _cacheService.ClearCache();
    return RedirectToAction(nameof(Index));
}
```

The code here is pretty self-explanatory, and we implement the 3 features we discussed in the Index, CacheUserAsync and CacheRemoveAsync actions.

> 💡 Tip: You would ideally want to decorate the UsersService class with CacheService by using a DI container such as [Scrutor](https://github.com/khellang/Scrutor). You don’t want to write the plumbing code we have written here to emulate a similar thing as the default DI container doesn’t support the behaviour. Refer to Andrew Lock’s [excellent article](https://andrewlock.net/adding-decorated-classes-to-the-asp.net-core-di-container-using-scrutor/#manually-creating-decorators-with-the-asp-net-core-di-container) on this topic.

I’m going to skip all the other plumbing code and show you how we Get and Set values with the Redis cache. The real magic happens in the ICacheProvider class.

The code itself it pretty self-explanatory. In the GetFromCache method, we call the `GetStringAsync` with a given key (`_Users` in this case). It’s worth noting that we need to deserialise it to the type we want before returning it to the caller. Similarly, we serialise our users list and save it as a string in the Redis cache under the `_Users` key.

[CacheProvider.cs](https://github.com/sahan91/DistributedCacheAspNetCoreRedis/blob/main/DistributedCache/Infrastructure/CacheProvider.cs)

```csharp
public class CacheProvider : ICacheProvider
{
    private readonly IDistributedCache _cache;

    public CacheProvider(IDistributedCache cache)
    {
        _cache = cache;
    }
    
    public async Task<T> GetFromCache<T>(string key) where T : class
    {
        var cachedResponse = await _cache.GetStringAsync(key);
        return cachedResponse == null ? null : JsonSerializer.Deserialize<T>(cachedResponse);
    }

    public async Task SetCache<T>(string key, T value, DistributedCacheEntryOptions options) where T : class
    {
        var response = JsonSerializer.Serialize(value);
        await _cache.SetStringAsync(key, response , options);
    }

    public async Task ClearCache(string key)
    {
        await _cache.RemoveAsync(key);
    }
}
```

### So what gets saved under the covers?

We can connect to the container and open up the `redis-cli` to see what’s inside. To do that, you could run the following command.

```bash
docker exec -it redis-cache redis-cli
```

Once you are in, you could issue a `hgetall _Users` command to inspect what’s inside the hash that got saved in our request.

![distributed-caching-in-aspdotnet-core-with-redis-5.png](https://sahansera.dev/static/17c68c3e7d0fc28660e1387f0f444d6b/5a190/distributed-caching-in-aspdotnet-core-with-redis-5.png "distributed-caching-in-aspdotnet-core-with-redis-5.png")

If you like to use a GUI, here’s a nice representation of what our web app saved under the hood. I used [RedisInsight](https://redislabs.com/redis-enterprise/redis-insight/) tool for this.

![distributed-caching-in-aspdotnet-core-with-redis-4.png](https://sahansera.dev/static/710a2fc71779bba994d84d293a3bcb25/5a190/distributed-caching-in-aspdotnet-core-with-redis-4.png "distributed-caching-in-aspdotnet-core-with-redis-4.png")

### Demo

Here’s a working demo when you run the code from my repo:

<img width="100%" style="width:100%" src="https://media.giphy.com/media/O9gYOJNhby8bdoxPuB/giphy.gif">

As you can see, it will only fetch the users list only the first time we click the “Cache It” button. Every subsequent request will fetch the users list from the Redis cache and serve to our app. The cache expiry can be configured by setting a sliding window or an absolute expiry by passing in the configuration. In this demo, I have set a sliding expiry for 2 minutes.

### Conclusion

In this article, we converted our previous In-Memory example to use the IDistributedCache interface provided by [ASP.NET](http://asp.NET) Core and used Redis as a backing store. This approach can be utilised to leverage cloud service such as Azure Redis Cache for use-cases such as response caching, session storage etc.

Hope you enjoyed this article and feel free to share your thoughts and feedback. Until next time 👋

### References

1. [https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0)
2. [https://redis.io/commands](https://redis.io/commands)
3. [https://aspnetcore.readthedocs.io/en/stable/performance/caching/distributed.html#using-a-redis-distributed-cache](https://aspnetcore.readthedocs.io/en/stable/performance/caching/distributed.html#using-a-redis-distributed-cache)