Interact with Azure Cache for Redis by using .NET

Completed

Typically, a client application uses a client library to form requests and execute commands on a Redis cache. You can get a list of client libraries directly from the Redis clients page.

Executing commands on the Redis cache

A popular high-performance Redis client for the .NET language is StackExchange.Redis. The package is available through NuGet and can be added to your .NET code using the command line or IDE. Following are examples of how to use the client.

Connecting to your Redis cache with StackExchange.Redis

Recall that we use the host address, port number, and an access key to connect to a Redis server. Azure also offers a connection string for some Redis clients that bundles this data together into a single string. It looks something like the following (with the cache-name and password-here fields filled in with real values):

[cache-name].redis.cache.windows.net:6380,password=[password-here],ssl=True,abortConnect=False

You can pass this string to StackExchange.Redis to create a connection the server.

Notice that there are two more parameters at the end:

  • ssl - ensures that communication is encrypted.
  • abortConnection - allows a connection to be created even if the server is unavailable at that moment.

There are several other optional parameters you can append to the string to configure the client library.

Creating a connection

The main connection object in StackExchange.Redis is the StackExchange.Redis.ConnectionMultiplexer class. This object abstracts the process of connecting to a Redis server (or group of servers). It's optimized to manage connections efficiently and intended to be kept around while you need access to the cache.

You create a ConnectionMultiplexer instance using the static ConnectionMultiplexer.Connect or ConnectionMultiplexer.ConnectAsync method, passing in either a connection string or a ConfigurationOptions object.

Here's a simple example:

using StackExchange.Redis;
...
var connectionString = "[cache-name].redis.cache.windows.net:6380,password=[password-here],ssl=True,abortConnect=False";
var redisConnection = ConnectionMultiplexer.Connect(connectionString);

Once you have a ConnectionMultiplexer, there are three primary things you might want to do:

  • Access a Redis Database.
  • Make use of the publisher/subscriber features of Redis, which is outside the scope of this module.
  • Access an individual server for maintenance or monitoring purposes.

Accessing a Redis database

The IDatabase type represents the Redis database. You can retrieve one using the GetDatabase() method:

IDatabase db = redisConnection.GetDatabase();

Tip

The object returned from GetDatabase is a lightweight object, and does not need to be stored. Only the ConnectionMultiplexer needs to be kept alive.

Once you have a IDatabase object, you can execute methods to interact with the cache. All methods have synchronous and asynchronous versions that return Task objects to make them compatible with the async and await keywords.

Following is an example of storing a key/value in the cache:

bool wasSet = db.StringSet("favorite:flavor", "i-love-rocky-road");

The StringSet method returns a bool indicating whether the value was set (true) or not (false). We can then retrieve the value with the StringGet method:

string value = db.StringGet("favorite:flavor");
Console.WriteLine(value); // displays: ""i-love-rocky-road""

Getting and Setting binary values

Recall that Redis keys and values are binary safe. These same methods can be used to store binary data. There are implicit conversion operators to work with byte[] types so you can work with the data naturally:

byte[] key = ...;
byte[] value = ...;

db.StringSet(key, value);
byte[] key = ...;
byte[] value = db.StringGet(key);

StackExchange.Redis represents keys using the RedisKey type. This class has implicit conversions to and from both string and byte[], allowing both text and binary keys to be used without any complication. Values are represented by the RedisValue type. As with RedisKey, there are implicit conversions in place to allow you to pass string or byte[].

Other common operations

The IDatabase interface includes several other methods to work with the Redis cache. There are methods to work with hashes, lists, sets, and ordered sets.

Here are some of the more common ones that work with single keys, you can read the source code for the interface to see the full list.

Method Description
CreateBatch Creates a group of operations to be sent to the server as a single unit, but not necessarily processed as a unit.
CreateTransaction Creates a group of operations to be sent to the server as a single unit and processed on the server as a single unit.
KeyDelete Delete the key/value.
KeyExists Returns whether the given key exists in cache.
KeyExpire Sets a time-to-live (TTL) expiration on a key.
KeyRename Renames a key.
KeyTimeToLive Returns the TTL for a key.
KeyType Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset, and hash.

Executing other commands

The IDatabase object has an Execute and ExecuteAsync method that can be used to pass textual commands to the Redis server. For example:

var result = db.Execute("ping");
Console.WriteLine(result.ToString()); // displays: "PONG"

The Execute and ExecuteAsync methods return a RedisResult object that is a data holder that includes two properties:

  • Resp2Type that returns a string indicating the type of the result - STRING, INTEGER, etc.
  • IsNull a true/false value to detect when the result is null.

You can then use ToString() on the RedisResult to get the actual return value.

You can use Execute to perform any supported commands - for example, we can get all the clients connected to the cache ("CLIENT LIST"):

var result = await db.ExecuteAsync("client", "list");
Console.WriteLine($"Type = {result.Resp2Type}\r\nResult = {result}");

This outputs all the connected clients:

Type = BulkString
Result = id=9469 addr=16.183.122.154:54961 fd=18 name=DESKTOP-AAAAAA age=0 idle=0 flags=N db=0 sub=1 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=subscribe numops=5
id=9470 addr=16.183.122.155:54967 fd=13 name=DESKTOP-BBBBBB age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 ow=0 owmem=0 events=r cmd=client numops=17

Storing more complex values

Redis is oriented around binary safe strings, but you can cache off object graphs by serializing them to a textual format - typically XML or JSON. For example, perhaps for our statistics, we have a GameStats object that looks like:

public class GameStat
{
    public string Id { get; set; }
    public string Sport { get; set; }
    public DateTimeOffset DatePlayed { get; set; }
    public string Game { get; set; }
    public IReadOnlyList<string> Teams { get; set; }
    public IReadOnlyList<(string team, int score)> Results { get; set; }

    public GameStat(string sport, DateTimeOffset datePlayed, string game, string[] teams, IEnumerable<(string team, int score)> results)
    {
        Id = Guid.NewGuid().ToString();
        Sport = sport;
        DatePlayed = datePlayed;
        Game = game;
        Teams = teams.ToList();
        Results = results.ToList();
    }

    public override string ToString()
    {
        return $"{Sport} {Game} played on {DatePlayed.Date.ToShortDateString()} - " +
               $"{String.Join(',', Teams)}\r\n\t" + 
               $"{String.Join('\t', Results.Select(r => $"{r.team } - {r.score}\r\n"))}";
    }
}

We could use the Newtonsoft.Json library to turn an instance of this object into a string:

var stat = new GameStat("Soccer", new DateTime(2019, 7, 16), "Local Game", 
                new[] { "Team 1", "Team 2" },
                new[] { ("Team 1", 2), ("Team 2", 1) });

string serializedValue = Newtonsoft.Json.JsonConvert.SerializeObject(stat);
bool added = db.StringSet("event:1950-world-cup", serializedValue);

We could retrieve it and turn it back into an object using the reverse process:

var result = db.StringGet("event:2019-local-game");
var stat = Newtonsoft.Json.JsonConvert.DeserializeObject<GameStat>(result.ToString());
Console.WriteLine(stat.Sport); // displays "Soccer"

Cleaning up the connection

When the connection is no longer needed, you can Dispose the ConnectionMultiplexer. This closes all connections and shutdown the communication to the server.

redisConnection.Dispose();
redisConnection = null;