Cross-client communication with SignalR, Angular and .NET 5
Tags: signalr, dotnet, angular, showdev
Published: 2022-01-09 16:13:47

More and more companies are using web application to provide their services. Building those you will face many common problems. One of them is the communication between different clients of the same web application, for example used for instant messaging or simultaneous editing in the browser, like done in Google Drive or Microsoft Office for the web . The general aim is updating information on one client when some changes are done on another client.

As usual, there are many ways to solve this problem. In this example we will use the stack of .NET 5 for the backend and Angular for the frontend, which allows us to easily use SignalR for the cross-client communication.

## Real-time updating with SignalR

[SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) is an open source project by Microsoft and can easily be integrated in a .NET application. Additionally, there is a npm package, which allows you to integrate it into your web frontend, for example your React or Angular application.

What does SignalR mainly do? It provides functionality to push updates from the server to the client, so information can be updated without refreshing the page. This makes your web application real-time ready.

How does SignalR achieve this? It tries to use different technologies for the communication depending on the support by client and server. So you don’t need to care about WebSockets, Polling or Server Sent Events, as SignalR already does.

## Our example - the Quizzer app

Let's build a real-time web application from scratch. As an example we take a Quiz app with a quiz master and several quiz candidates. Each of them will be represented by a browser instance. If the quiz master starts the next question, it should be pushed to all the candidates' clients. If a candidate makes a guess, the quiz master should be informed. And of course, if a candidate joins the quiz, they should set a name and notify the quiz master about this. So in this example we do not just want to push updates from the server to the clients, but push updates from one client to the other clients. As we can't do this directly because the clients don't know of each others, we do this cross-client communication with the help of the server.


![QuizzerCommunication](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rou7mwh9ysv4zvqfh67.png)

## Prerequisites

To follow this tutorial and build the app yourself, you need to have some things installed on your machine:

- .NET 5 SDK and runtime
- Visual Studio 2019 or higher
- node.js v16 or higher with npm v8 or higher
- Angular v13 or higher

You can find the complete source code [here](https://github.com/sbittis/Quizzer). 

## Getting started with the .NET 5 backend

First we need to create our backend in Visual Studio. Hit "Create new project" and choose "ASP.NET Core-Web-API" as the project template. It will create a project with the standard setup for a Web API and a controller, which is exactly what we need.

In order to use SignalR in our backend, we need to enable it in the *Startup.cs* as follows:

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

As we are already here, we can also do another thing which will enable local debugging of frontend and backend together. We configure CORS to allow [localhost](http://localhost) as an origin:

```csharp
public class Startup
{
		readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

		// ...

		public void ConfigureServices(IServiceCollection services)
        {
        services.AddCors(options =>
        {
            options.AddPolicy(MyAllowSpecificOrigins, builder =>
            {
                builder
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials()
                    .WithOrigins("http://localhost:4200")
                    .Build();
            });
        });
				// ...
		}
		// ...
}
```

We allow [localhost](http://localhost) at port 4200 as an origin because this is where our Angular application will run by default. So we ensure that API calls by the frontend won’t be blocked.

One central element of SignalR is the so called *Hub*. We’ll come to the details later, but we already register our *QuizHub* in the *Startup.cs* by using the *MapHub()* function:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
		// ...
		app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapHub<QuizHub>("/hub");
        });
}
```

## Working with the SignalR Hub

As we just registered our *QuizHub*, of course we need to implement it. SignalR provides an abstract class called *Hub* and we need to inherit from this class:

```csharp
using Microsoft.AspNetCore.SignalR;

public class QuizHub : Hub
{
}
```

In this class we will now add methods that can be called by the clients:

```csharp
public async Task MakeGuess(string player, string answer)
{
    await this.Clients.All.SendAsync("GuessMade", player, answer);
}

public async Task SetPlayerName(string playerName)
{
    await this.Clients.All.SendAsync("PlayerNameSet", playerName);
}
```

If a client calls one of these methods, all other clients that listen to the specific event (*GuessMade* or *PlayerNameSet*) will be informed and get the data passed by the calling client. We will see how we can actually do those calls when we come to the frontend part.

## Getting the quiz data

Let’s care for questions and answers now, so we can properly test our example later. You could build your own quiz data in a database, but for this example we use the [Open Trivia DB](https://opentdb.com/) which provides us with thousands of questions. We can simply retrieve the data via their REST API. In the query we can specify the amount, type and difficulty of the questions we want to get back, so the API call

> [https://opentdb.com/api.php?amount=1&difficulty=medium&type=multiple](https://opentdb.com/api.php?amount=1&difficulty=medium&type=multiple)
> 

will return one multiple choice question with medium difficulty.

The process of getting the question is implemented in *Data/QuestionProvider.cs*. In the *Data* folder, you can also find two model classes which help deserialize the API response.

The whole retrieval of the data is then encapsulated in *QuizQuestionRepository.cs* which provides only one method *GetNextQuestion()* that can be used in the controller.

## Setting up the QuizController

The *QuizController* provides a method for the quiz master to go to the next question. Apart from the *QuizHub* itself, we can also send events to all listening clients in the controller. To do so, we need to get an instance of *IHubContext* from the *QuizHub* via dependency injection:

```csharp
public QuizController(IHubContext<QuizHub> hubContext)
{
    _hubContext = hubContext;
}
```

Once we have this *IHubContext*, we can use it to call the same method as we do in the *QuizHub* and populate the new question:

```csharp
[HttpGet("Next")]
public async Task NextQuestion()
{
    var question = await this.repository.GetNextQuestion();
    await _hubContext.Clients.All.SendAsync("GoToQuestion", question);
}
```

And that’s all we need from the backend side of things. Let’s move on to the frontend.

## Creating the Angular application

Open Visual Studio Code and type `ng new QuizzerFrontend` in the terminal to get a fresh Angular setup. We need to create three components. One *HomeComponent* to choose if the client is the quiz master or a candidate and the *QuizMasterComponent* and *QuizCandidateComponent* for each of them. For the details of these components please visit the source code on GitHub.

We also need *quiz.service.ts* to interact with the backend and call the endpoint provided by our *QuizController*. Here we implement the API call:

```tsx
nextQuestion(): Subscription {
  return this.http
    .get(this.url + 'Next')
    .pipe(catchError(this.handleError)).subscribe();
}
```

## Implementing the SignalR communication in the frontend

Let’s rush to the interesting parts now! We create a new service *signalr.service.ts* to manage all the communication through SignalR. To be able to do so, we need to install the SignalR package by executing `npm install @microsoft/signalr`.

From this package we use the *HubConnectionBuilder* first to create a *HubConnection*:

```tsx
private buildConnection(): HubConnection {
  return new HubConnectionBuilder()
    .withUrl(this.url)
    .withAutomaticReconnect()
    .build();
}
```

The url is in this case the backend url, where we configured our *QuizHub* earlier in the *Startup.cs*, i.e. [https://localhost:44375/hub/](https://localhost:44375/hub/). Once that’s done, we need to register on the events, our client should listen to:

```tsx
private registerOnServerEvents(): void {
  this.hubConnection.on('GoToQuestion', (question: any) => {
    this.questionReceived$.next(question);
  });

  this.hubConnection.on('GuessMade', (player: string, guess: string) => {
    this.guessMade$.next({ player, guess });
  });

  this.hubConnection.on('PlayerNameSet', (playerName: string) => {
    this.setPlayerName$.next(playerName);
  });
}
```

At last we need to start the connection by calling `start()` on our *HubConnection* instance. The *SignalRService* is used in the components, which take their actions on the Subjects that populate the events sent by the SignalR hub, so they can properly react on those events.

And this is all, we have created a Quizzer app with cross-client communication to let our quiz master and candidates interact!

[Just download the source code and try it out!](https://github.com/sbittis/Quizzer)

This article was initially published at [https://from-scratch.de](https://from-scratch.de/cross-client-communication-signalr).