Exercise - Connect an app to the cache
Now that our Redis cache is created in Azure, let's create an application to use it. Make sure you have your connection string information from the Azure portal.
Note
The integrated Cloud Shell is available on the right. You can use that command prompt to create and run the example code we're building here, or perform these steps locally if you have a .NET Core development environment set up.
Create a console application
We'll use a console application so we can focus on the Redis implementation.
In the Cloud Shell, create a new .NET Core Console Application and name it
SportsStatsTracker
.dotnet new console --name SportsStatsTracker
Change the current directory to the folder for the new project.
cd SportsStatsTracker
Add the connection string
Let's add the connection string we got from the Azure portal into the code. Never store credentials like this in your source code. To keep this sample simple, we're going to use a configuration file. A better approach for a server-side application in Azure would be to use Azure Key Vault with certificates.
Create a new appsettings.json file to add to the project.
touch appsettings.json
Open the code editor by entering
code .
in the project folder. If you're working locally, we recommend using Visual Studio Code. The steps here will mostly align with its usage.Select the appsettings.json file in the editor, and add the following text. Paste your connection string into the value of the setting.
{ "CacheConnection": "[value-goes-here]" }
Save the changes.
Important
Whenever you paste or change code into a file in the editor, make sure to save afterwards using the ... menu, or the accelerator key (Ctrl+S on Windows and Linux, Cmd+S on macOS).
Select the SportsStatsTracker.csproj file in the editor to open it.
Add the following
<ItemGroup>
configuration block into the root<Project>
element below the<PropertyGroup>
element. This configuration will include the new file in the project and copy it to the output folder. The instructions in this block will ensure that the app configuration file is placed in the output directory when the app is compiled/built.<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> ... </PropertyGroup> <ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> </Project>
Save the file.
Important
If you don't save the file, you'll lose the change when you add the package later.
Add support to read a JSON configuration file
A .NET Core application requires other NuGet packages to read a JSON configuration file.
In the command prompt section of the window, add a reference to the Microsoft.Extensions.Configuration.Json NuGet package:
dotnet add package Microsoft.Extensions.Configuration.Json
Add code to read the configuration file
Now that we've added the required libraries to enable reading configuration, we need to enable that functionality within our console application.
Select Program.cs in the editor. Replace the contents of the file with the following code:
using Microsoft.Extensions.Configuration; namespace SportsStatsTracker { class Program { static void Main(string[] args) { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); } } }
The
using
statement lets us access the libraries to read the configuration, and the code in theMain
method initializes the configuration system to read from the appsettings.json file.
Get the connection string from configuration
In Program.cs, at the end of the Main method, use the new config variable to retrieve the connection string, and store it in a new variable named connectionString.
The config variable has an indexer where you can pass in a string to retrieve from your appSettings.json file.
string connectionString = config["CacheConnection"];
Add support for the Redis cache .NET client
Next, let's configure the console application to use the StackExchange.Redis client for .NET.
Add the StackExchange.Redis NuGet package to the project using the command prompt at the bottom of the Cloud Shell editor.
dotnet add package StackExchange.Redis
Select Program.cs in the editor, and add a
using
for the namespace StackExchange.Redisusing StackExchange.Redis;
After the installation is completed, the Redis cache client is available to use with your project.
Connect to the cache
Let's add the code to connect to the cache.
Select Program.cs in the editor.
Create a
ConnectionMultiplexer
usingConnectionMultiplexer.Connect
by passing it your connection string. Name the returned value cache.Because the created connection is disposable, wrap it in a
using
block. Your code should look something like the following.string connectionString = config["CacheConnection"]; using (var cache = ConnectionMultiplexer.Connect(connectionString)) { }
Note
The connection to Azure Cache for Redis is managed by the ConnectionMultiplexer
class. This class should be shared and reused throughout your client application. We don't want to create a new connection for each operation. Instead, we want to store it off as a field in our class and reuse it for each operation. Here, we're only going to use it in the Main method, but in a production application, it should be stored in a class field or a singleton.
Add a value to the cache
Now that we have the connection, let's add a value to the cache.
Inside the
using
block after the connection has been created, use theGetDatabase
method to retrieve anIDatabase
instance:IDatabase db = cache.GetDatabase();
Call
StringSet
on theIDatabase
object to set the key "test:key" to the value "some value".
The return value from StringSet
is a bool
indicating whether the key was added.
Display the return value from
StringSet
onto the console:bool setValue = db.StringSet("test:key", "some value"); Console.WriteLine($"SET: {setValue}");
Get a value from the cache
Next, retrieve the value using
StringGet
. This method takes the key to retrieve and returns the value.Output the returned value:
string? getValue = db.StringGet("test:key"); Console.WriteLine($"GET: {getValue}");
Your code should look like this:
using System; using Microsoft.Extensions.Configuration; using System.IO; using StackExchange.Redis; namespace SportsStatsTracker { class Program { static void Main(string[] args) { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); string connectionString = config["CacheConnection"]; using (var cache = ConnectionMultiplexer.Connect(connectionString)) { IDatabase db = cache.GetDatabase(); bool setValue = db.StringSet("test:key", "some value"); Console.WriteLine($"SET: {setValue}"); string? getValue = db.StringGet("test:key"); Console.WriteLine($"GET: {getValue}"); } } } }
Run the application to see the result. Enter
dotnet run
in the terminal window below the editor. Make sure you're in the project folder, or it won't find your code to build and run.dotnet run
Tip
If the program doesn't do what you expect, but compiles, it might be because you haven't saved changes in the editor. Always remember to save changes as you switch between the terminal and the editor windows.
Use the async versions of the methods
We've been able to get and set values from the cache, but we're using the older synchronous versions of these methods. In server-side applications, these methods aren't an efficient use of our threads. Instead, we want to use the asynchronous versions. You can easily spot them; they all end in Async.
To make these methods easy to work with, we can use the C# async
and await
keywords. If you're using your own .NET Core development environment instead of the integrated Cloud Shell, you must use at least C# 7.1 to be able to apply these keywords to the Main method.
Apply the async keyword
To apply the async
keyword to the Main method. We'll have to do two things.
Add the
async
keyword onto the Main method signature.Change the return type from
void
toTask
.using Microsoft.Extensions.Configuration; using StackExchange.Redis; namespace SportsStatsTracker { class Program { static async Task Main(string[] args) { ...
Get and set values asynchronously
We can leave the synchronous methods in place. Let's add a call to the StringSetAsync
and StringGetAsync
methods to add another value to the cache. Set counter to the value 100.
Use the
StringSetAsync
andStringGetAsync
methods to set and retrieve a key named counter. Set the value to 100.Apply the
await
keyword to get the results from each method.Output the results to the console window, just as you did with the synchronous versions:
// Simple get and put of integral data types into the cache setValue = await db.StringSetAsync("counter", "100"); Console.WriteLine($"SET: {setValue}"); getValue = await db.StringGetAsync("counter"); Console.WriteLine($"GET: {getValue}");
Run the application again. It should still work, and now have two values.
Increment the value
Use the
StringIncrementAsync
method to increment your counter value. Pass the number 50 to add to the counter:Notice that the method takes the key and either a
long
ordouble
.Depending on the parameters passed, it either returns a
long
ordouble
.
Output the results of the method to the console.
long newValue = await db.StringIncrementAsync("counter", 50); Console.WriteLine($"INCR new value = {newValue}");
Other operations
Finally, let's try executing a few more methods with the ExecuteAsync
support.
Execute PING to test the server connection. It should respond with PONG.
Execute FLUSHDB to clear the database values. It should respond with OK:
var result = await db.ExecuteAsync("ping"); Console.WriteLine($"PING = {result.Type} : {result}"); result = await db.ExecuteAsync("flushdb"); Console.WriteLine($"FLUSHDB = {result.Type} : {result}");
The final code should look something like the following:
using Microsoft.Extensions.Configuration;
using StackExchange.Redis;
namespace SportsStatsTracker
{
class Program
{
static async Task Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
string connectionString = config["CacheConnection"];
using (var cache = ConnectionMultiplexer.Connect(connectionString))
{
IDatabase db = cache.GetDatabase();
bool setValue = db.StringSet("test:key", "some value");
Console.WriteLine($"SET: {setValue}");
string getValue = db.StringGet("test:key");
Console.WriteLine($"GET: {getValue}");
setValue = await db.StringSetAsync("counter", "100");
Console.WriteLine($"SET: {setValue}");
getValue = await db.StringGetAsync("counter");
Console.WriteLine($"GET: {getValue}");
long newValue = await db.StringIncrementAsync("counter", 50);
Console.WriteLine($"INCR new value = {newValue}");
var result = await db.ExecuteAsync("ping");
Console.WriteLine($"PING = {result.Type} : {result}");
result = await db.ExecuteAsync("flushdb");
Console.WriteLine($"FLUSHDB = {result.Type} : {result}");
}
}
}
}
When you run the application again, you should see the following output:
SET: True
GET: some value
SET: True
GET: 100
INCR new value = 150
PING = SimpleString : PONG
FLUSHDB = SimpleString : OK
Challenge
As a challenge, try serializing an object type to the cache. Here are the basic steps.
Create a new
class
with some public properties. You can invent one of your own ("Person" or "Car" are popular), or use the "GameStats" example given in the previous unit.Add support for the Newtonsoft.Json NuGet package using
dotnet add package
.Add a
using
for theNewtonsoft.Json
namespace.Create one of your objects.
Serialize it with
JsonConvert.SerializeObject
and useStringSetAsync
to push it into the cache.Get it back from the cache with
StringGetAsync
and then deserialize it withJsonConvert.DeserializeObject<T>
.
Now that our Redis cache is created in Azure, let's create an application to use it. Make sure you have your connection information from the Azure portal.
Note
The integrated Cloud Shell is available on the right. You can use that command prompt to create and run the example code we are building here, or perform these steps locally if you have a Node.js development environment set up.
Create a Console Application
We'll use a console application so we can focus on the Redis implementation.
In the Cloud Shell, create a new directory called
redisapp
, and initialize a new Node.js app there.mkdir redisapp cd redisapp npm init -y touch app.js
Our app will use the following npm packages:
- redis: The most commonly used JavaScript package for connecting to Redis.
- bluebird: Used to convert the callback-style methods in the
redis
package to an awaitable Promise. - dotenv: Loads environment variables from a
.env
file, which is where we'll store our Redis connectivity information.
Let's install them now. Run this command to add them to our app:
npm install redis bluebird dotenv
Add configuration
Let's add the connection information we got from the Azure portal into a .env
configuration file.
Create a new .env file to the project:
touch .env
Open the code editor by entering
code .
in the project folder. If you're working locally, we recommend using Visual Studio Code. The steps here will mostly align with its usage.Select the .env file in the editor and paste in the following text:
REDISHOSTNAME= REDISKEY= REDISPORT=
Paste in the hostname, primary key, and port after the equals sign on each respective line. The complete file will look similar to the following example:
REDISHOSTNAME=myredishost.redis.cache.windows.net REDISKEY=K21mLSMN++z8d1FvIeMGy3VOAgoOmqaNYCqeE44eMDc= REDISPORT=6380
Save the file with Ctrl+S on Windows and Linux or Cmd+S on macOS.
Set up the implementation
Now, it's time to write the code for our application.
Select app.js in the editor.
First, we'll add our
require
statements. Paste in the following code at the top of the file.var Promise = require("bluebird"); var redis = require("redis");
Next, we'll load our
.env
configuration, and use bluebird'spromisifyAll
function to convert theredis
package's functions and methods to an awaitable Promise. Paste in the following code:require("dotenv").config(); Promise.promisifyAll(redis);
Now, we'll initialize a Redis client. Paste in the boilerplate code from the previous unit (using
process.env
to access our host name, port and key) to create the client:const client = redis.createClient( process.env.REDISPORT, process.env.REDISHOSTNAME, { password: process.env.REDISKEY, tls: { servername: process.env.REDISHOSTNAME } } );
Use the client to work with the cache
We're ready to write code to interact with our Redis cache.
First, we'll add an
async
function wrapper at the bottom of the file to contain our main code. We need this wrapper to be able toawait
the asynchronous function calls we'll be using. All of the rest of the code we'll be adding in this unit will be in this wrapper.(async () => { // The rest of the code you'll paste in goes here. })();
Add a value to the cache with the
setAsync
method, and read it back withgetAsync
:console.log("Adding value to the cache"); await client.setAsync("myKey", "myValue"); console.log("Reading value back:"); console.log(await client.getAsync("myKey"));
Send a ping to the cache with
pingAsync
:console.log("Pinging the cache"); console.log(await client.pingAsync());
Delete all the keys in the cache with
flushdbAsync
:await client.flushdbAsync();
Finally, close the connection with
quitAsync
:await client.quitAsync();
Save the file. Your finished application should look like this:
var Promise = require("bluebird"); var redis = require("redis"); require("dotenv").config(); Promise.promisifyAll(redis); const client = redis.createClient( process.env.REDISPORT, process.env.REDISHOSTNAME, { password: process.env.REDISKEY, tls: { servername: process.env.REDISHOSTNAME } } ); (async () => { console.log("Adding value to the cache"); await client.setAsync("myKey", "myValue"); console.log("Reading value back:"); console.log(await client.getAsync("myKey")); console.log("Pinging the cache"); console.log(await client.pingAsync()); await client.flushdbAsync(); await client.quitAsync(); })();
Run the application. In the Cloud Shell, execute the following command.
node app.js
You'll see the following results.
Adding value to the cache Reading value back: myValue Pinging the cache PONG