Tutorial: Get started with Relay Hybrid Connections WebSockets in .NET
In this quickstart, you create .NET sender and receiver applications that send and receive messages by using Hybrid Connections WebSockets in Azure Relay. To learn about Azure Relay in general, see Azure Relay.
In this quickstart, you take the following steps:
- Create a Relay namespace by using the Azure portal.
- Create a hybrid connection in that namespace by using the Azure portal.
- Write a server (listener) console application to receive messages.
- Write a client (sender) console application to send messages.
- Run applications.
Prerequisites
To complete this tutorial, you need the following prerequisites:
- Visual Studio 2015 or later. The examples in this tutorial use Visual Studio 2017.
- An Azure subscription. If you don't have one, create a free account before you begin.
Create a namespace
Sign in to the Azure portal.
Select All services on the left menu. Select Integration, search for Relays, move the mouse over Relays, and then select Create.
On the Create namespace page, follow these steps:
Choose an Azure subscription in which to create the namespace.
For Resource group, choose an existing resource group in which to place the namespace, or create a new one.
Enter a name for the Relay namespace.
Select the region in which your namespace should be hosted.
Select Review + create at the bottom of the page.
On the Review + create page, select Create.
After a few minutes, you see the Relay page for the namespace.
Get management credentials
On the Relay page, select Shared access policies on the left menu.
On the Shared access policies page, select RootManageSharedAccessKey.
Under SAS Policy: RootManageSharedAccessKey, select the Copy button next to Primary Connection String. This action copies the connection string to your clipboard for later use. Paste this value into Notepad or some other temporary location.
Repeat the preceding step to copy and paste the value of Primary key to a temporary location for later use.
Create a hybrid connection
On the Relay page for your namespace, follow these steps to create a hybrid connection.
On the left menu, Under Entities, select Hybrid Connections, and then select + Hybrid Connection.
On the Create Hybrid Connection page, enter a name for the hybrid connection, and select Create.
Create a server application (listener)
In Visual Studio, write a C# console application to listen for and receive messages from the relay.
Create a console application
In Visual Studio, create a new Console App (.NET Framework) project.
Add the Relay NuGet package
- Right-click the newly created project, and then select Manage NuGet Packages.
- Select Browse, and then search for Microsoft.Azure.Relay. In the search results, select Microsoft Azure Relay.
- Select Install to complete the installation. Close the dialog box.
Write code to receive messages
At the top of the Program.cs file, replace the existing
using
statements with the followingusing
statements:using System; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Net; using Microsoft.Azure.Relay;
Add constants to the
Program
class for the hybrid connection details. Replace the placeholders with the values that you obtained when you created the hybrid connection. Be sure to use the fully qualified namespace name.// replace {RelayNamespace} with the name of your namespace private const string RelayNamespace = "YOUR-RELAY-NAMESPACE-NAME.servicebus.windows.net"; // replace {HybridConnectionName} with the name of your hybrid connection private const string ConnectionName = "HYBRID-CONNECTION-NAME"; // replace {SAKKeyName} with the name of your Shared Access Policies key, which is RootManageSharedAccessKey by default private const string KeyName = "SAS-KEY-NAME"; // replace {SASKey} with the primary key of the namespace you saved earlier private const string Key = "SAS-KEY-VALUE";
Add the
ProcessMessagesOnConnection
method to theProgram
class:// The method initiates the connection. private static async void ProcessMessagesOnConnection(HybridConnectionStream relayConnection, CancellationTokenSource cts) { Console.WriteLine("New session"); // The connection is a fully bidrectional stream. // Put a stream reader and a stream writer over it. // This allows you to read UTF-8 text that comes from // the sender, and to write text replies back. var reader = new StreamReader(relayConnection); var writer = new StreamWriter(relayConnection) { AutoFlush = true }; while (!cts.IsCancellationRequested) { try { // Read a line of input until a newline is encountered. var line = await reader.ReadLineAsync(); if (string.IsNullOrEmpty(line)) { // If there's no input data, signal that // you will no longer send data on this connection, // and then break out of the processing loop. await relayConnection.ShutdownAsync(cts.Token); break; } // Write the line on the console. Console.WriteLine(line); // Write the line back to the client, prepended with "Echo:" await writer.WriteLineAsync($"Echo: {line}"); } catch (IOException) { // Catch an I/O exception. This likely occurred when // the client disconnected. Console.WriteLine("Client closed connection"); break; } } Console.WriteLine("End session"); // Close the connection. await relayConnection.CloseAsync(cts.Token); }
Add the
RunAsync
method to theProgram
class:private static async Task RunAsync() { var cts = new CancellationTokenSource(); var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key); var listener = new HybridConnectionListener(new Uri(string.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider); // Subscribe to the status events. listener.Connecting += (o, e) => { Console.WriteLine("Connecting"); }; listener.Offline += (o, e) => { Console.WriteLine("Offline"); }; listener.Online += (o, e) => { Console.WriteLine("Online"); }; // Opening the listener establishes the control channel to // the Azure Relay service. The control channel is continuously // maintained, and is reestablished when connectivity is disrupted. await listener.OpenAsync(cts.Token); Console.WriteLine("Server listening"); // Provide callback for the cancellation token that will close the listener. cts.Token.Register(() => listener.CloseAsync(CancellationToken.None)); // Start a new thread that will continuously read the console. new Task(() => Console.In.ReadLineAsync().ContinueWith((s) => { cts.Cancel(); })).Start(); // Accept the next available, pending connection request. // Shutting down the listener allows a clean exit. // This method returns null. while (true) { var relayConnection = await listener.AcceptConnectionAsync(); if (relayConnection == null) { break; } ProcessMessagesOnConnection(relayConnection, cts); } // Close the listener after you exit the processing loop. await listener.CloseAsync(cts.Token); }
Add the following line of code to the
Main
method in theProgram
class:RunAsync().GetAwaiter().GetResult();
The completed Program.cs file should look like this:
namespace Server { using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Relay; public class Program { private const string RelayNamespace = "{RelayNamespace}.servicebus.windows.net"; private const string ConnectionName = "{HybridConnectionName}"; private const string KeyName = "{SASKeyName}"; private const string Key = "{SASKey}"; public static void Main(string[] args) { RunAsync().GetAwaiter().GetResult(); } private static async Task RunAsync() { var cts = new CancellationTokenSource(); var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key); var listener = new HybridConnectionListener(new Uri(string.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider); // Subscribe to the status events. listener.Connecting += (o, e) => { Console.WriteLine("Connecting"); }; listener.Offline += (o, e) => { Console.WriteLine("Offline"); }; listener.Online += (o, e) => { Console.WriteLine("Online"); }; // Opening the listener establishes the control channel to // the Azure Relay service. The control channel is continuously // maintained, and is reestablished when connectivity is disrupted. await listener.OpenAsync(cts.Token); Console.WriteLine("Server listening"); // Provide callback for a cancellation token that will close the listener. cts.Token.Register(() => listener.CloseAsync(CancellationToken.None)); // Start a new thread that will continuously read the console. new Task(() => Console.In.ReadLineAsync().ContinueWith((s) => { cts.Cancel(); })).Start(); // Accept the next available, pending connection request. // Shutting down the listener allows a clean exit. // This method returns null. while (true) { var relayConnection = await listener.AcceptConnectionAsync(); if (relayConnection == null) { break; } ProcessMessagesOnConnection(relayConnection, cts); } // Close the listener after you exit the processing loop. await listener.CloseAsync(cts.Token); } private static async void ProcessMessagesOnConnection(HybridConnectionStream relayConnection, CancellationTokenSource cts) { Console.WriteLine("New session"); // The connection is a fully bidrectional stream. // Put a stream reader and a stream writer over it. // This allows you to read UTF-8 text that comes from // the sender, and to write text replies back. var reader = new StreamReader(relayConnection); var writer = new StreamWriter(relayConnection) { AutoFlush = true }; while (!cts.IsCancellationRequested) { try { // Read a line of input until a newline is encountered. var line = await reader.ReadLineAsync(); if (string.IsNullOrEmpty(line)) { // If there's no input data, signal that // you will no longer send data on this connection. // Then, break out of the processing loop. await relayConnection.ShutdownAsync(cts.Token); break; } // Write the line on the console. Console.WriteLine(line); // Write the line back to the client, prepended with "Echo:" await writer.WriteLineAsync($"Echo: {line}"); } catch (IOException) { // Catch an I/O exception. This likely occurred when // the client disconnected. Console.WriteLine("Client closed connection"); break; } } Console.WriteLine("End session"); // Close the connection. await relayConnection.CloseAsync(cts.Token); } } }
Create a client application (sender)
In Visual Studio, write a C# console application to send messages to the relay.
Create a console application
In Visual Studio, create a new Console App (.NET Framework) project.
Add the Relay NuGet package
- Right-click the newly created project, and then select Manage NuGet Packages.
- Select Browse, and then search for Microsoft.Azure.Relay. In the search results, select Microsoft Azure Relay.
- Select Install to complete the installation. Close the dialog box.
Write code to send messages
At the top of the Program.cs file, replace the existing
using
statements with the followingusing
statements:using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Relay;
Add constants to the
Program
class for the hybrid connection details. Replace the placeholders with the values that you obtained when you created the hybrid connection. Be sure to use the fully qualified namespace name.// replace {RelayNamespace} with the name of your namespace private const string RelayNamespace = "YOUR-RELAY-NAMESPACE-NAME.servicebus.windows.net"; // replace {HybridConnectionName} with the name of your hybrid connection private const string ConnectionName = "HYBRID-CONNECTION-NAME"; // replace {SAKKeyName} with the name of your Shared Access Policies key, which is RootManageSharedAccessKey by default private const string KeyName = "SAS-KEY-NAME"; // replace {SASKey} with the primary key of the namespace you saved earlier private const string Key = "SAS-KEY-VALUE";
Add the following method to the
Program
class:private static async Task RunAsync() { Console.WriteLine("Enter lines of text to send to the server with ENTER"); // Create a new hybrid connection client. var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key); var client = new HybridConnectionClient(new Uri(String.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider); // Initiate the connection. var relayConnection = await client.CreateConnectionAsync(); // Run two concurrent loops on the connection. One // reads input from the console and writes it to the connection // with a stream writer. The other reads lines of input from the // connection with a stream reader and writes them to the console. // Entering a blank line shuts down the write task after // sending it to the server. The server then cleanly shuts down // the connection, which terminates the read task. var reads = Task.Run(async () => { // Initialize the stream reader over the connection. var reader = new StreamReader(relayConnection); var writer = Console.Out; do { // Read a full line of UTF-8 text up to newline. string line = await reader.ReadLineAsync(); // If the string is empty or null, you are done. if (String.IsNullOrEmpty(line)) break; // Write to the console. await writer.WriteLineAsync(line); } while (true); }); // Read from the console and write to the hybrid connection. var writes = Task.Run(async () => { var reader = Console.In; var writer = new StreamWriter(relayConnection) { AutoFlush = true }; do { // Read a line from the console. string line = await reader.ReadLineAsync(); // Write the line out, also when it's empty. await writer.WriteLineAsync(line); // Quit when the line is empty, if (String.IsNullOrEmpty(line)) break; } while (true); }); // Wait for both tasks to finish. await Task.WhenAll(reads, writes); await relayConnection.CloseAsync(CancellationToken.None); }
Add the following line of code to the
Main
method in theProgram
class.RunAsync().GetAwaiter().GetResult();
The Program.cs should look like this:
using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Relay; namespace Client { class Program { private const string RelayNamespace = "{RelayNamespace}.servicebus.windows.net"; private const string ConnectionName = "{HybridConnectionName}"; private const string KeyName = "{SASKeyName}"; private const string Key = "{SASKey}"; static void Main(string[] args) { RunAsync().GetAwaiter().GetResult(); } private static async Task RunAsync() { Console.WriteLine("Enter lines of text to send to the server with ENTER"); // Create a new hybrid connection client. var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(KeyName, Key); var client = new HybridConnectionClient(new Uri(String.Format("sb://{0}/{1}", RelayNamespace, ConnectionName)), tokenProvider); // Initiate the connection. var relayConnection = await client.CreateConnectionAsync(); // Run two concurrent loops on the connection. One // reads input from the console and then writes it to the connection // with a stream writer. The other reads lines of input from the // connection with a stream reader and then writes them to the console. // Entering a blank line shuts down the write task after // sending it to the server. The server then cleanly shuts down // the connection, which terminates the read task. var reads = Task.Run(async () => { // Initialize the stream reader over the connection. var reader = new StreamReader(relayConnection); var writer = Console.Out; do { // Read a full line of UTF-8 text up to newline. string line = await reader.ReadLineAsync(); // If the string is empty or null, you are done. if (String.IsNullOrEmpty(line)) break; // Write to the console. await writer.WriteLineAsync(line); } while (true); }); // Read from the console and write to the hybrid connection. var writes = Task.Run(async () => { var reader = Console.In; var writer = new StreamWriter(relayConnection) { AutoFlush = true }; do { // Read a line from the console. string line = await reader.ReadLineAsync(); // Write the line out, also when it's empty. await writer.WriteLineAsync(line); // Quit when the line is empty. if (String.IsNullOrEmpty(line)) break; } while (true); }); // Wait for both tasks to finish. await Task.WhenAll(reads, writes); await relayConnection.CloseAsync(CancellationToken.None); } } }
Note
The sample code in this article uses a connection string to authenticate to an Azure Relay namespace to keep the tutorial simple. We recommend that you use Microsoft Entra ID authentication in production environments, rather than using connection strings or shared access signatures, which can be more easily compromised. For detailed information and sample code for using the Microsoft Entra ID authentication, see Authenticate and authorize an application with Microsoft Entra ID to access Azure Relay entities and Authenticate a managed identity with Microsoft Entra ID to access Azure Relay resources.
Run the applications
Run the server application.
Run the client application and enter some text.
Ensure that the server application console displays the text that was entered in the client application.
Related content
In this quickstart, you created .NET client and server applications that used WebSockets to send and receive messages. The Hybrid Connections feature of Azure Relay also supports using HTTP to send and receive messages. To learn how to use HTTP with Azure Relay Hybrid Connections, see the HTTP quickstart.
In this quickstart, you used .NET Framework to create client and server applications. To learn how to write client and server applications using Node.js, see the Node.js WebSockets quickstart or the Node.js HTTP quickstart.