C# – Redis Cache

By | 06/04/2022

In this post, we will see how to manage Redis Cache in a Web API .NET Core project.
But first of all, what is Redis Cache?
From Redis web site:
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

We start creating a new Web API project called TestRedis where, we will install the Microsoft.Extensions.Caching.StackExchangeRedis library running, in the Package Manager Console, the command:

Install-Package Microsoft.Extensions.Caching.StackExchangeRedis

Then, we add the entity User and a repository class for managing CRUD operations:

[USER.CS]

namespace TestRedis.Data
{
    public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}



[IUSERREPOSITORY.CS]

using System.Collections.Generic;
using System.Threading.Tasks;

namespace TestRedis.Data
{
    public interface IUserRepository
    {
        Task<List<User>> GetUsers();
        Task<User> GetUserByKey(int key);
        Task AddUser(User newUser);
        Task DeleteUser(int key);
    }
}



[USERREPOSITORY.CS]

using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System;
using System.Linq;
using StackExchange.Redis;
using Microsoft.Extensions.Configuration;

namespace TestRedis.Data
{
    public class UserRepository : IUserRepository
    {
        private readonly IDistributedCache _redisCache;
        private readonly IConfiguration _config;

        public UserRepository(IDistributedCache redisCache, IConfiguration config)
        {
            _redisCache = redisCache;
            _config = config;
        }

        public async Task AddUser(User newUser)
        {
            await SaveIntoCache(newUser);
        }


        public async Task<List<User>> GetUsers()
        {
            var listUsers = new List<User>();
            List<string> redisKeys = GetAllkeys();

            if(redisKeys.Count>0)
            {
                foreach(string key in redisKeys)
                {
                    var userFromCache = await _redisCache.GetAsync(key);
                    if(userFromCache!=null)
                    {
                        // we take User from cache
                        var serializedUser = Encoding.UTF8.GetString(userFromCache);
                        var userOut = JsonConvert.DeserializeObject<User>(serializedUser);
                        listUsers.Add(userOut);
                    }
                }
            }
            else
            {
                // we create a list of User
                listUsers.Add(new User { Id = 1, Email = "email1_fromDB", UserName = "user1_fromDB", Password = "password1_fromDB" });
                listUsers.Add(new User { Id = 2, Email = "email2_fromDB", UserName = "user2_fromDB", Password = "password2_fromDB" });
                listUsers.Add(new User { Id = 3, Email = "email3_fromDB", UserName = "user3_fromDB", Password = "password3_fromDB" });

                // we save a list of user into Redis
                await SaveIntoCache(new User { Id = 1, Email = "email1_fromCache", UserName = "user1_fromCache", Password = "password1_fromCache" });
                await SaveIntoCache(new User { Id = 2, Email = "email2_fromCache", UserName = "user2_fromCache", Password = "password2_fromCache" });
                await SaveIntoCache(new User { Id = 3, Email = "email3_fromCache", UserName = "user3_fromCache", Password = "password3_fromCache" });
            }

            return listUsers;
        }

        public async Task<User> GetUserByKey(int key)
        {
            User userOut = null;
            var userFromCache = await _redisCache.GetAsync(key.ToString());
            if (userFromCache != null)
            {
                var serializedUser = Encoding.UTF8.GetString(userFromCache);
                userOut = JsonConvert.DeserializeObject<User>(serializedUser);
            }

            return userOut;
        }

        public async Task DeleteUser(int key)
        {
            await _redisCache.RemoveAsync(key.ToString());
        }


        private async Task SaveIntoCache(User user)
        {
            var serializedUser = JsonConvert.SerializeObject(user);
            var redisUsers = Encoding.UTF8.GetBytes(serializedUser);
            var options = new DistributedCacheEntryOptions()
                .SetAbsoluteExpiration(DateTime.Now.AddMinutes(10))
                .SetSlidingExpiration(TimeSpan.FromMinutes(8));

            await _redisCache.SetAsync(user.Id.ToString(), redisUsers, options);
        }

        private List<string> GetAllkeys()
        {
            List<string> listKeys = new List<string>();
            using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(_config.GetValue<string>("RedisCache:ServerUrl")))
            {
                string host = _config.GetValue<string>("RedisCache:ServerUrl").Split(':')[0];
                int port = Convert.ToInt32(_config.GetValue<string>("RedisCache:ServerUrl").Split(':')[1]);
                var keys = redis.GetServer(host, port).Keys();
                listKeys.AddRange(keys.Select(key => (string)key).ToList());
            }

            return listKeys;
        }
    }
}



Now, we create the controller that we will use to manage the CRUD operations:

[USERCONTROLLER.CS]

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using TestRedis.Data;

namespace TestRedis.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly IUserRepository _userRepository;

        public UserController(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        [HttpGet]
        public async Task<IActionResult> GetAllUsers()
        {
            return Ok(await _userRepository.GetUsers());
        }

        [HttpGet("{key}")]
        public async Task<IActionResult> GetUserByKey(int key)
        {
            User user = await _userRepository.GetUserByKey(key);

            if (user == null)
            {
                return NotFound();
            }

            return Ok(user);
        }

        [HttpDelete("{key}")]
        public async Task<IActionResult> DeleteUser(int key)
        {
            await _userRepository.DeleteUser(key);

            return NoContent();
        }

        [HttpPost]
        public async Task<IActionResult> AddUser(User user)
        {
            await _userRepository.AddUser(user);

            return NoContent();
        }
    }
}



Finally, we add in appsettings.json a parameter for Redis Cache and then, we modify the Startup file in order to set up Redis Cache:

[APPSETTINGS.JSON]

{
  "RedisCache": {
    "ServerUrl": "localhost:6379"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}



[STARTUP.CS]

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using TestRedis.Data;

namespace TestRedis
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Definition of Dependency Injection 
            services.AddScoped<IUserRepository, UserRepository>();

            // Definition of Redis Cache configuration
            services.AddStackExchangeRedisCache(options =>
            {
                options.Configuration = Configuration.GetSection("RedisCache").GetSection("ServerUrl").Value;
            });

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestRedis", Version = "v1" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TestRedis v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}



Finally, for creating a Redis Cache Docker container, we have to run the command :

docker run -d -p 6379:6379 --name test-redis redis



Now, if we run the application, this will be the results:

LIST USERS


GET USER BY KEY


ADD USER


DELETE USER



REDIS CLI
Redis CLI (Redis Command Line Interface) allows us to send commands to Redis and read the replies sent by the server, directly from the terminal.
In order to execute the bash command inside the container, we have to run the command:

docker exec -it test-redis /bin/bash

and then, we can run some Redis CLI commands:


CHECK REDIS IS WORKING

ping


LIST OF KEYS

redis-cli --scan


DELETE ALL KEYS

redis-cli flushall


GET VALUE FROM A KEY

hgetall KEY


For all information of Redis CLI, go to the web site: https://redis.io/topics/rediscli



REDIS CACHE – CODE
https://github.com/ZoneOfDevelopment/TestRedis



Category: C# Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *