Creating a chatroom using WebSockets and SignalR
Tags: csharp, webdev
Published: 2020-05-22 12:50:25

Since [WebAssembly/WASM](https://webassembly.org/) has finally gotten past the 1.0 milestone including official support in the most common browsers, it's time to take a quick dive into the world of interactive and real-time browser applications. To be more specific, one of the enabling web technologies that I think will impact the WASM based applications -> Web Sockets.

Some of the common use-cases where websockets are being used today includes live-tracking services for things like stock tickers and cloud server loads. With the addition of WASM to the browsers toolbox, we can expect performance- and graphically heavy web applications, for example online games directly in the browser. A perfect use-case for websockets enabling the underlying networking needs.

## Why SignalR?
[SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) is built for real-time, 2-way communication and is backed by web sockets. It's possible to implement web sockets without, but by using SignalR, we gain some nice benefits out of the box.

Usually scaling can be a trouble with websockets since it requires much more than simple tcp requests. Cloud providers like Azure offers dedicated instances designed for SignalR applications. In addition to this, the load can be spread over multiple instances using caching layers, which gives a shared knowledge between all application instances of all the connected clients.

Another great benefit is the built-in client connection handling (and fallback to old-school polling, in case web sockets are not supported by the client browser) and the custom SignalR message protocol. Out of the box, web sockets simply sends a message to the server and that's it. SignalR enables the developer to write custom event-based handling by defining methods on both the server and client. These can then be invoked directly when interacting with the SignalR connection. This is a great addition to reduce initial development time and speeding up the process of creating prototypes!

## The application 
### Backend / .NET Core
Enabling SignalR is easy. In fact, the SignalR package is included by default in newer versions of .ASPNET Core. To enable SignalR, the services needs to be registered and the hub endpoint defined. The SignalR hub is then the "interface" used when the server receives messages from the clients.

```csharp
public void ConfigureServices(IServiceCollection services)
{
        // ...

	services.AddSignalR();
}

public void Configure(IApplicationBuilder app)
{
        // ...

	app.UseEndpoints(endpoints =>
	{
		endpoints.MapRazorPages();
		endpoints.MapHub<ChatHub>("/_ws/chat");
	});
}
```

For the hub itself, we've overridden the Connected and Disconnected events for clients. The client connection state is handled by SignalR, including the possibility to add reconnection logic out of the box.

We've added a method named `Send`. Whenever a client wishes to send a new message to our server, they simply call invoke on the current connection and pass the name of the given event - in this case, send, which looks like `connection.invoke('send', username, messageInput.value);`. The arguments for the Send-method is likewise passed down as arguments for the Hub Send method.

```csharp
public class ChatHub : Hub
{
	public override async Task OnConnectedAsync()
	{
		var name = Context.GetHttpContext().Request.Query["name"];
		await Clients.All.SendAsync("user.connected", name, Context.ConnectionId);
	}

	public override async Task OnDisconnectedAsync(Exception exception)
	{
		await Clients.All.SendAsync("user.disconnected", Context.ConnectionId);
	}

	public async Task Send(string name, string message)
	{
		await Clients.All.SendAsync("message.received", name, message);
	}
}
```

### Frontend / JavaScript
The frontend client uses the SignalR JavaScript library `@microsoft/signalr`. Add it as a reference and it's ready to go. 

For our frontend, a simple UI has been created which contains a list of connected users, a box for all received messages and an input field to type and send the messages. The enabling JavaScript behind sets up our chatroom by creating a SignalR connection to our server at the specified hub endpoint.

The method `connection.on(...)` sets up our supported events for which the client will react to when a message is received. This definition of named events is a part of the SignalR layer that sits on top of web sockets.

```javascript
document.addEventListener('DOMContentLoaded', function () {
	var username = 'person_' + Math.floor((Math.random() * 1000) + 1);

	function sendMessage(event) {
		var messageInput = document.getElementById('sendInput');
		connection.invoke('send', username, messageInput.value);
		messageInput.value = '';
		messageInput.focus();
		event.preventDefault();
	}

	var connection = new signalR.HubConnectionBuilder()
		.withUrl('/_ws/chat?name=' + username)
		.build();

	connection.start()
		.then(function () {
			document.getElementById('sendBtn').addEventListener('click', sendMessage);
		})
		.catch(error => {
			console.error(error.message);
		});

	connection.on('user.connected', function (name, id) {
		var usersElement = document.getElementById('connectedUsers');
		var newUserElement = document.createElement('li');
		newUserElement.id = id;
		newUserElement.innerHTML = name;
		
		usersElement.appendChild(newUserElement);
	});

	connection.on('user.disconnected', function (id) {
		document.getElementById(id).remove();
	});

	connection.on('message.received', function (name, message) {
		var messageElement = document.createElement('article');
		var content = '<p><strong>' + name + '</strong><br>' + message + '</p>';

		messageElement.classList.add("media");
		messageElement.innerHTML = '<figure class="media-left"><p class="image is-64x64"><img src="/media/placeholder.png"></p></figure><div class="media-content"><div class="content messageContent">' + content + '</div></div>';

		document.getElementById('messages').appendChild(messageElement);
	});
});
```

## In action
When ws request is initiated by the browser/client, it creates an upgrade request with the expectation of the server to accept and open a socket.

![Upgrade](https://dev-to-uploads.s3.amazonaws.com/i/ljpple7yu519h9furj6q.png)

When accepted and connected, Chrome keeps track on received messages. Normal requests would simply contain a response, but web sockets are different. This flow contains 2 clients connecting and sending messages to each other. This also gives insight into how SignalR handles messages between the server and client.

![Tracing](https://dev-to-uploads.s3.amazonaws.com/i/lsulzatd54c6ms817ikx.png)
![Example](https://dev-to-uploads.s3.amazonaws.com/i/2biuhqo0xdpgs5svagt5.png)