Using Azure Cache For Redis

Introduction

In today’s article, we will take a look at using Azure Cache for Redis. In our applications, we often need to cache certain information to speed up the performance. We have a number of options to do this but most of them do not scale very well. On the Microsoft Cloud, we have a great solution which is the Azure Cache for Redis. It provides us a solution to cache information for lookups, store session information, etc. It is very scalable, and we can scale it according to our needs. Today, we will see how we can set it up in Microsoft Azure and use it in our .NET application. So, let us begin.

Creating the Azure Cache for Redis service in Microsoft Azure

Open the portal and follow the following steps,

Using Azure Cache for Redis

Search for Azure Cache for Redis.

Using Azure Cache for Redis

Select create and enter the below details.

Using Azure Cache for Redis

Using Azure Cache for Redis

Finally, click create to create the Redis Cache.

Using Azure Cache for Redis

Once created click on the “Access Keys” option as below.

Using Azure Cache for Redis

From here select and copy the Primary connection string.

Creating the .NET application to use this cache

Create a new console application in Visual Studio 2022 community preview edition.

Using Azure Cache for Redis

Using Azure Cache for Redis

Using Azure Cache for Redis

Using Azure Cache for Redis

We now have the application as below:

Using Azure Cache for Redis

Next, add the required NuGet package (StackExchange.Redis)

Using Azure Cache for Redis

Using Azure Cache for Redis

Using Azure Cache for Redis

Using Azure Cache for Redis

Then add a new class that will hold all the Redis initialization and operational functions.

Using Azure Cache for Redis

Also, add another class “Employee” to demonstrate caching a class object.

Using Azure Cache for Redis

Finally, let us add the NuGet package to serialize and de-serialize our employee class (Newtonsoft.Json).

Using Azure Cache for Redis

Using Azure Cache for Redis

Using Azure Cache for Redis

Now, add the below code to the “RedisInitializer.cs” class.

using StackExchange.Redis;
using System.Net.Sockets;
namespace ConsoleAppAzureRedisCache {
    public static class RedisInitializer {
        public static Lazy < ConnectionMultiplexer > lazyConnection = CreateConnection();
        public static ConnectionMultiplexer Connection {
            get {
                return lazyConnection.Value;
            }
        }
        public static Lazy < ConnectionMultiplexer > CreateConnection() {
            return new Lazy < ConnectionMultiplexer > (() => {
                return ConnectionMultiplexer.Connect("<Insert Azure Cache for Redis connection string here>");
            });
        }
        private static long lastReconnectTicks = DateTimeOffset.MinValue.UtcTicks;
        private static DateTimeOffset firstErrorTime = DateTimeOffset.MinValue;
        private static DateTimeOffset previousErrorTime = DateTimeOffset.MinValue;
        private static readonly object reconnectLock = new object();
        public static TimeSpan ReconnectMinFrequency => TimeSpan.FromSeconds(60);
        public static TimeSpan ReconnectErrorThreshold => TimeSpan.FromSeconds(30);
        public static int RetryMaxAttempts => 5;
        public static void CloseConnection() {
            if (lazyConnection == null) return;
            try {
                lazyConnection.Value.Close();
            } catch (Exception) {}
        }
        public static void ForceReconnect() {
            var utcNow = DateTimeOffset.UtcNow;
            long previousTicks = Interlocked.Read(ref lastReconnectTicks);
            var previousReconnectTime = new DateTimeOffset(previousTicks, TimeSpan.Zero);
            TimeSpan elapsedSinceLastReconnect = utcNow - previousReconnectTime;
            if (elapsedSinceLastReconnect < ReconnectMinFrequency) return;
            lock(reconnectLock) {
                utcNow = DateTimeOffset.UtcNow;
                elapsedSinceLastReconnect = utcNow - previousReconnectTime;
                if (firstErrorTime == DateTimeOffset.MinValue) {
                    firstErrorTime = utcNow;
                    previousErrorTime = utcNow;
                    return;
                }
                if (elapsedSinceLastReconnect < ReconnectMinFrequency) return;
                TimeSpan elapsedSinceFirstError = utcNow - firstErrorTime;
                TimeSpan elapsedSinceMostRecentError = utcNow - previousErrorTime;
                bool shouldReconnect = elapsedSinceFirstError >= ReconnectErrorThreshold && elapsedSinceMostRecentError <= ReconnectErrorThreshold;
                previousErrorTime = utcNow;
                if (!shouldReconnect) return;
                firstErrorTime = DateTimeOffset.MinValue;
                previousErrorTime = DateTimeOffset.MinValue;
                Lazy < ConnectionMultiplexer > oldConnection = lazyConnection;
                CloseConnection();
                lazyConnection = CreateConnection();
                Interlocked.Exchange(ref lastReconnectTicks, utcNow.UtcTicks);
            }
        }
        private static T BasicRetry < T > (Func < T > func) {
            int reconnectRetry = 0;
            int disposedRetry = 0;
            while (true) {
                try {
                    return func();
                } catch (Exception ex) when(ex is RedisConnectionException || ex is SocketException) {
                    reconnectRetry++;
                    if (reconnectRetry > RetryMaxAttempts) throw;
                    ForceReconnect();
                }
                catch (ObjectDisposedException) {
                    disposedRetry++;
                    if (disposedRetry > RetryMaxAttempts) throw;
                }
            }
        }
        public static IDatabase GetDatabase() {
            return BasicRetry(() => Connection.GetDatabase());
        }
        public static System.Net.EndPoint[] GetEndPoints() {
            return BasicRetry(() => Connection.GetEndPoints());
        }
        public static IServer GetServer(string host, int port) {
            return BasicRetry(() => Connection.GetServer(host, port));
        }
    }
}

Add the below code to the “Employee.cs” class

namespace ConsoleAppAzureRedisCache {
    public class Employee {
        public int Id {
            get;
            set;
        }
        public string ? Name {
            get;
            set;
        }
        public int Age {
            get;
            set;
        }
    }
}

Finally, add the below code to the “Program.cs” file

using StackExchange.Redis;
using ConsoleAppAzureRedisCache;
using Newtonsoft.Json;
IDatabase cache = RedisInitializer.GetDatabase();
string cacheCommand = "PING";
Console.WriteLine("\nCache command  : " + cacheCommand);
Console.WriteLine("Cache response : " + cache.Execute(cacheCommand).ToString());
cacheCommand = "SET Message \"This value has been set from the .NET app!\"";
Console.WriteLine("\nCache command  : " + cacheCommand);
Console.WriteLine("Cache response : " + cache.StringSet("Message", "This value has been set from the .NET app!").ToString());
cacheCommand = "GET Message";
Console.WriteLine("\nCache command  : " + cacheCommand);
Console.WriteLine("Cache response : " + cache.StringGet("Message").ToString());
var employee = new Employee {
    Id = 1, Name = "John Doe", Age = 25
};
var empValue = JsonConvert.SerializeObject(employee);
Console.WriteLine("Cache response : " + cache.StringSet("Employee", empValue));
Console.WriteLine("Cache response : " + cache.StringGet("Employee").ToString());
var employeeFromCache = JsonConvert.DeserializeObject < Employee > (cache.StringGet("Employee").ToString());
Console.WriteLine($ "Employee from Cache details - Id: {employeeFromCache?.Id}, Name:{employeeFromCache?.Name}, Age: {employeeFromCache?.Age}");
RedisInitializer.CloseConnection();

Now, we will compile and run our application and see the below output.

Using Azure Cache for Redis

Hence, we see how easy it is to setup the Cache and use it in our .NET applications. We just need to embed the connection string and we are ready to go.

Summary

In this article, we took a look at the Azure Cache for Redis feature. We saw how we set it up and use it from our .NET application. It is a very scalable and secure solution for our caching and session storage requirements. Happy coding!