Levvel Blog - Real-Time Web Communication Using SignalR and .NET Core

Real-Time Web Communication Using SignalR and .NET Core

So, you’ve decided you want to build a web app that will involve real-time communication. Congratulations! You might be planning anything from a company-wide chat application that will support a total of 10 employees to a multiplayer video game complete with rocket ships, lasers, and thousands of users logging on and off. In any case, delivering a quality product has never been easier.

SignalR is a library that was first introduced by Microsoft in 2013 to enable .NET developers to add real-time communication to their applications as smoothly as possible, but its use was limited to developers who either intended to host their applications in Windows environments or were willing to fight through the pain of building via Mono.

The release of ASP.NET Core 2.1 RC1 on May 7th gave us SignalR baked in as a full-fledged citizen, and this encouragement, “This is a ‘go live’ release that can be used in production with the understanding that you will need to update to the final stable release once it is available.” This means that SignalR applications can be run natively on any platform supported by .NET Core, including Windows, MacOS, and Linux.

History

The web as we know it is made up of clients—computers that request information, and servers—computers that provide information upon request. The HTTP 1.0 spec (circa 1996) gave us GET, HEAD, PUT, POST, DELETE, and more, allowing clients to contact a server to exchange information. This means getting data from a client to a server has been supported via HTTP since the earliest days of the internet.

The trouble with real-time communication like chat is that two clients getting real-time updates from one another generally requires the first client sending data to a server, and the server, in turn, sending that data to a second client. Until relatively recently, there was no good way for a server to send a client a message.

One of the most common workarounds for this problem was called long polling. Essentially, a client would send an HTTP request with a long timeout. The server would keep this request open until it had data to push and then respond to the open request with the relevant data in the HTTP response. This had the drawback of being resource intensive on the server because of the numerous open HTTP requests, which meant it didn’t scale well.

Over time, other real-time communications methods evolved. Internet Explorer introduced Forever Frame, a technique in which a hidden iframe makes a request to a server endpoint that never completes, providing a way for the server to send information directly to the client.

Unfortunately, a new connection needed to be created for each message that would be sent, consuming server resources and creating a barrier for scalable applications.

In 2006, the Opera web browser was the first to incorporate Server-Sent Events (SSE), a technology that allows servers to push data to clients through an open HTTP connection. Today, SSE is supported by most major browsers with the notable exceptions of Internet Explorer and Microsoft Edge.

Finally, in 2011, a new technology called WebSockets was standardized by the ITEF. WebSockets provide a persistent connection between the client and server, allowing both parties to send data at any time. WebSocket connections are initiated by the client through a process called the WebSocket handshake. The handshake begins with the client sending a plain old HTTP GET request with a header like this:

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Note the Upgrade header that requests an upgrade to WebSockets, the Sec-Websocket-Key header that will be used by the server in the next piece, and the Sec-WebSocket-Version header that indicates the WebSocket version to use.

Assuming the server supports the WebSocket version requested, it will agree to establish the WebSocket communication by sending an HTTP Response like this:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Note the Sec-WebSocket-Accept header in the response is formed by concatenating the Sec-WebSocket-Key with the guid 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, taking the SHA1 hash of the result, and returning the base64 encoding of this hash. This complicated process exists to prevent security loopholes that would pop up if the server accepted the WebSocket connection but interpreted the data as an HTTP request.

Once a successful HTTP response is returned, a WebSocket handshake is complete, and two-way messages can be sent over the TCP connection—no HTTP messages required!

Back to the Present

Back to your web app. Let’s say you’re building a basic chat application that you can use to talk to your family online. You know that you want to use WebSockets because it’s the fastest, most secure, and most scalable web communication protocol currently available to you.

While WebSockets will work great for your family members running modern browsers, what about your Uncle Herbert? Uncle Herbert’s home computer runs Windows XP sp2, with Internet Explorer 9 as his only browser. This is what he sees when he logs in:

This is the beauty of SignalR. When a client that doesn’t support WebSockets connects to your application, SignalR falls back to using older methods of communication, shown below:

This means that with SignalR, you can build a web application that allows you and Uncle Herbert to chat together using the latest technology that each of your machines support, without needing to write extra code. SignalR will detect that your browser supports WebSockets and initiate a WebSocket connection while simultaneously connecting Uncle Herbert’s browser via long polling, and neither of you will know the difference. SignalR also abstracts the implementation of WebSockets away from the developer so there’s no need to worry about updating your code when new implementations of the WebSocket protocol come out.

A Quick Note About Socket.io

You may have heard of Socket.IO. Like SignalR, it’s a technology that enables real-time communication over the web by attempting a WebSocket connection, then dropping back to long polling when WebSockets is not supported.

Socket.IO includes a JavaScript server-side component that is intended to be used with Node.js. Until the production-ready release of .NET Core SignalR on May 7 of this year, Socket.IO was one of the few enterprise safe real-time messaging technologies that could be deployed natively to a Linux or Unix environment. Now, that has changed.

This post isn’t meant to provide a direct comparison of the two technologies or try to sway you to use one over the other. It’s just important to point out that SignalR is now a contender.

Getting Started

For this tutorial, I’m using MacOs, Visual Studio Code, .NET Core SDK 2.1.300-preview1, and Node.js 8.11.2 (only needed for the npm package manager). You should be able to roughly follow along on any OS and with any text editor, provided that you have .NET Core SDK 2.1.300-preview1 or higher installed.

To start, fire up the terminal, and create a new project:

dotnet new mvc -au Individual -name SignalRMVCTutorial

Breaking this down: dotnet new mvc creates a new, empty, .NET Core MVC application, -au Individual sets up individual authentication. Users will be prompted to create accounts using a combination of username and password. --name SignalRMVCTutorial names our project.

Next, enter the directory and run dotnet build to build the project. Users of early versions of .NET Core will note that calling dotnet restore is no longer necessary as it is now called implicitly by the build command.

cd SignalRMVCTutorial
dotnet build

Now, let’s take a look at our web app before we’ve even written a line of code. Type dotnet run and hit enter:

As we can see from the output, our app should be running over https at https://localhost:5001—fire up your favorite browser, navigate there, and check out what you’ve got:

If you’re like me, you haven’t configured the Kester web server that’s running your .NET Core application to use an SSL certificate, so you’re seeing an HTTPS error. I’m going to ignore the error by clicking Advanced -> Proceed to localhost (unsafe). If this workaround doesn’t meet your needs, you can create a self-signed SSL certificate and configure Kestrel to use it by following these instructions.

Now, we see the application:

What we get out of the box is impressive, especially considering we haven’t written a single line of code yet. I’m going to go to the ‘Login’ screen and click ‘Register as a new user’ to create an account:

Now that you’re familiar with the out-of-the-box app, let’s get coding. Kill the running process in the terminal by pressing CTRL+C and launch Visual Studio Code by typing code .

Let’s start by creating a place for the chat functionality to actually take place—a new view.

First, navigate to HomeController.cs and add this code:

public IActionResult Chat()
{
  return View();
}

This addition means that the Home Controller will try to return the view “Chat” if it is available—so let’s create it. Navigate to Views > Home, right-click, and choose “New File”. Name this file Chat.cshtml. Paste the following code in the file:

@{
    ViewBag.Title = "Chat";
}

<h1>Chat</h1>

<form id="send-form" action="#">
    Send a message: 
    <input type="text" id="message-textbox" disabled /> 
    <button id="send-button" type="submit" disabled>Send</button>
</form>

Now, we should have a place to build the chat functionality. To make sure, fire up your browser and navigate to the view we just created: https://localhost:5001/Home/Chat

It’s now just a matter of adding the functionality. Let’s start with the server side. We’ll need to create a SignalR Hub, so create a new folder in your project called “Hubs” and create a new file called “ChatHub.cs” inside it. In this file, paste the following code:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

namespace SignalRTutorial.Hubs
{
   public class ChatHub : Hub
   {
       [Authorize]
       public override async Task OnConnectedAsync()
       {
           await Clients.All.SendAsync("SendAction", Context.User.Identity.Name, "joined");
       }

       public override async Task OnDisconnectedAsync(Exception ex)
       {
           await Clients.All.SendAsync("SendAction", Context.User.Identity.Name, "left");
       }

       public async Task Send(string message)
       {
           await Clients.All.SendAsync("SendMessage", Context.User.Identity.Name, message);
       }
   }
}

This Hub will update all clients when a new client connects, an old client disconnects, or a client enters chat info. Now we just need to configure our application to use it. To do this, Navigate to Startup.cs, and add a reference to the newly created Hubs namespace. Then add the following lines of code to the ConfigureServices and Configure methods:

...
using SignalRMVCTutorial.Hubs;


public void ConfigureServices(IServiceCollection services)
{
  ....
  services.AddSignalR();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  ...
  app.UseSignalR(route =>
  {
    route.MapHub<ChatHub>("/chat");
  });
}

That’s it for the server side, now let’s focus on the client. For the client piece, you will need to reference the signalr javascript library. We will install this library using npm.

npm install @aspnet/signalr

For this tutorial, we’re just going to copy the signalr.min.js file that has been downloaded to ../node_modules/@aspnet/signalr/dist/browser and move it to be with all of our other referenced javascript. We’ll create a new folder in wwwroot/lib called signalr, and move our file there:

mkdir wwwroot/lib/signalr
mv node_modules/\@aspnet/signalr/dist/browser/signalr.min.js wwwroot/lib/signalr/

Now we navigate back to our chat.cshtml, reference the javascript file we just downloaded, and write out our front-end functionality, add the following javascript to our Chat.cshtml file:

@section Scripts {
    <script src="/lib/signalr/signalr.min.js"></script>

    <script type="text/javascript">
        // Bind DOM elements
        var sendForm = document.getElementById("send-form");
        var sendButton = document.getElementById("send-button");
        var messagesList = document.getElementById("messages-list");
        var messageTextBox = document.getElementById("message-textbox");

        function appendMessage(content) {
            var li = document.createElement("li");
            li.innerText = content;
            messagesList.appendChild(li);
        }

        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chat")
            .configureLogging(signalR.LogLevel.Information)
            .build();

        sendForm.addEventListener("submit", function() {
            var message = messageTextBox.value;
            messageTextBox.value = "";
            connection.send("Send", message);
        });

        connection.on("SendMessage", function (sender, message) {
            appendMessage(sender + ': ' + message);
        });

        connection.on("SendAction", function (sender, action) {
            appendMessage(sender + ' ' + action);
        });

        connection.start().then(function() {
            messageTextBox.disabled = false;
            sendButton.disabled = false;
        });
    </script>
}

That’s it, you’re done—your bare bones SingalR application using .net core is ready for deployment:

Another happy family brought together by the power of SignalR!

Will Owens

Will Owens

Consultant

Will Owens is a Consultant at Levvel. He has a strong background in software development, as well as a passion for learning and sharing new technologies.

Related Posts