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,
Search for Azure Cache for Redis.
Select create and enter the below details.
Finally, click create to create the Redis Cache.
Once created click on the “Access Keys” option as below.
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.
We now have the application as below:
Next, add the required NuGet package (StackExchange.Redis)
Then add a new class that will hold all the Redis initialization and operational functions.
Also, add another class “Employee” to demonstrate caching a class object.
Finally, let us add the NuGet package to serialize and de-serialize our employee class (Newtonsoft.Json).
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.
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!