Cross-Client-Kommunikation

Cross-Client-Kommunikation in Webanwendungen

Anwendungen mit Angular und .NET 5 durch SignalR um Echtzeit-Aktualisierungen erweitern – anhand eines praktischen Beispiels

Ein Anwendungsbericht von Sebastian Bittis, Senior Software Architect im Bereich Data Hub Development bei SMF

Mehr und mehr Unternehmen setzen auf Webanwendungen, um ihre Dienste bereitzustellen. Doch bei der Entwicklung stößt man auf viele allgemeine Probleme, die es zu lösen gilt. Eines davon ist die Kommunikation zwischen Clients derselben Webanwendung, zum Beispiel für Chats oder die gleichzeitige Bearbeitung von Dokumenten im Browser mit Google Drive oder Office365. Das allgemeine Ziel: Informationen auf einem Client aktualisieren, sobald Änderungen auf einem anderen Client durchgeführt werden.

Dieser Beitrag gibt eine kurze Einführung in SignalR und zeigt den Einsatz an einem praktischen Beispiel. Technologisch wird auf einem Stack aus .NET 5 und Angular aufgebaut. Damit lässt sich die Integration besonders einfach umsetzen, sie ist aber natürlich auch auf Basis anderer Technologien möglich.

SignalR: Beschreibung, Einsatzbereich und Vorteile

SignalR ist eine Open-Source-Softwarebibliothek von Microsoft, die sich backend-seitig sehr einfach in eine .NET-Anwendung integrieren lässt. Für das Frontend gibt es zusätzlich ein npm-Paket, um SignalR zum Beispiel in eine Angular- oder React-Anwendung einzubinden. Mittels SignalR-Funktionalität lassen sich Informationen vom Server an die Clients übermitteln. Dazu wird im Hintergrund automatisch auf verschiedene Technologien zurückgegriffen, die abhängig von der Unterstützung der verwendeten Server- und Client-Systeme sind. Als Entwickler:in muss man sich also keine Gedanken über WebSockets, Long Polling oder Server Sent Events machen, denn SignalR tut das bereits und abstrahiert die konkrete Technologie.

Ist SignalR einmal integriert, können sich zum Beispiel alle aktiven Clients über die Aktualisierungen eines anderen Clients benachrichtigen lassen oder bestimmte Events auf dem Server können an die Clients kommuniziert werden.

Praktisches Beispiel: die Quizzer-App

Um die Anwendung und die Vorteile von SignalR an einem konkreten Beispiel sehen zu können, erstellen wir Schritt für Schritt eine Webanwendung, zu deren Hauptanforderungen die Cross-Client-Kommunikation gehört.

Die Quizzer-App enthält einen „Quizmaster“ und mehrere Kandidat*innen. Sie alle werden als je eine Browser-Instanz repräsentiert. Sobald der Quizmaster eine neue Frage startet, soll sie auf den Clients aller Kandidat*innen angezeigt werden. Wenn diese dann ihren Tipp abgeben, soll wiederum der Quizmaster darüber informiert werden. Zusätzlich sollen Kandidat*innen beim Beitreten zu einem Quiz ihren Namen angeben können und der Quizmaster auch darüber informiert werden. All diese Anforderungen spiegeln wider, dass die Kommunikation sowohl vom Server zum Client (neue Frage ausspielen) als auch von Client zu Client (über neue Kandidat*innen benachrichtigen) notwendig ist.

Cross-Client-Kommunikation

Voraussetzungen

Um diesem Tutorial zu folgen und/oder es nachzubauen sind folgende Installationen notwendig:

  • .NET 5 SDK und Laufzeitumgebung
  • Visual Studio 2019 oder neuer und Visual Studio Code
  • node.js v16 oder neue mit npm v8 oder neuer
  • Angular 13 oder neuer

Der gesamte Quellcode für das Beispiel ist hier zu finden.

Starten mit dem .NET 5 Backend

Zuerst erstellen wir das Backend in Visual Studio über „Neues Projekt erstellen“. Wir wählen das Projekttemplate „ASP.NET Core-Web-API“, welches uns ein Projekt mit dem Standardaufsatz für eine Web API und einen Controller erstellt.

Um SignalR im Backend nutzen zu können, müssen wir es in der Startup.cs aktivieren:

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

An dieser Stelle ist noch eine zweite Einstellung zu aktivieren, um später Backend und Frontend auf dem Entwicklungsrechner miteinander kommunizieren lassen zu können. Wir konfigurieren CORS (Cross-Orgin Resource Sharing) so, dass localhost:4200, wo später unser Angular-Frontend laufen wird, als Ursprung erlaubt ist:

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();
            });
        });
     // ...
   }
   // ...
}

Dies tun wir, damit API-Aufrufe durch das Frontend nicht blockiert werden (bitte nicht in Produktion übernehmen!).

Unser zentrales Element aus SignalR ist der so genannte Hub. Was genau sich dahinter verbirgt, klären wir später, aber wir registrieren unseren QuizHub an dieser Stelle schon einmal, indem wir die MapHub()-Funktion verwenden:

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

Arbeiten mit dem SignalR-Hub

Wir haben den QuizHub also gerade registriert, müssen ihn aber natürlich noch implementieren. SignalR bietet eine abstrakte Klasse Hub an, von der wir dazu erben können:

using Microsoft.AspNetCore.SignalR;
public class QuizHub : Hub
{
}

In dieser Klasse fügen wir Methoden hinzu, die von den Clients aufgerufen werden können:

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);
}

Wenn ein Client eine dieser Methoden aufruft, werden alle anderen Clients, die auf dieses konkrete Event (GuessMade oder PlayerNameSet) lauschen, informiert und erhalten die Daten, die der aufrufende Client sendet. Wie genau diese Aufrufe funktionieren, sehen wir später anhand des Frontends.

Beispieldaten für das Quiz bereitstellen

Um unsere Beispielanwendung testen zu können, benötigen wir Testdaten, also Quizfragen, Antwortmöglichkeiten und die richtige Antwort. Für dieses Beispiel wird die Open Trivia DB genutzt, welche tausende Fragen zur Verfügung stellt. Die Daten werden per REST API bereitgestellt und können durch eine einfache Abfrage mit Parametern für Anzahl, Typ und Schwierigkeit der Fragen zurückgegeben werden. Der Aufruf

gibt eine einzelne Multiple-Choice-Frage mit mittlerer Schwierigkeit zurück.

Die Funktionalität für das Abrufen einer neuen Frage ist im Beispiel in der Klasse Data/QuestionProvider.cs implementiert. Im Ordner Data gibt es zusätzlich zwei Modell-Klassen, die zur Deserialisierung der API-Antwort genutzt werden.

Die Rückgabe der Daten an sich ist in der Klasse QuizQuestionRepository.cs gekapselt, die die Methode GetNextQuestion() anbietet. Diese kann wiederum im Controller genutzt werden.

Aufsatz des QuizControllers

Der QuizController bietet eine Methode für den Quizmaster an, um zur nächsten Frage zu gehen. Außerhalb vom QuizHub selbst, gibt es auch hier im Controller die Möglichkeit, Events an alle horchenden Clients zu senden. Um dies zu erreichen, benötigen wir zuerst eine Instanz des IHubContext. Auf diese können wir per Dependency Injection zugreifen:

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

Sobald der IHubContext verfügbar ist, können wir ihn analog zum QuizHub nutzen, um eine neue Frage zu verteilen:

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

Das ist alles, was backend-seitig zu tun ist. Als nächstes widmen wir uns dem Frontend.

Erstellen der Angular-Applikation

In Visual Studio Code erstellen wir per ng new QuizzerFrontend über die Konsole eine neue Angular-Anwendung. In unserem Frontend brauchen wir drei Komponenten. Eine HomeComponent um auswählen zu können, ob der Client Quizmaster oder Kandidat:in ist und eine QuizMasterComponent und QuizCandidateComponent für die beiden Rollen. Die konkrete Implementierung ist im Quellcode zu finden.

Zur Interaktion mit dem Backend nutzen wir die quiz.service.ts. Darin werden Methoden bereitgestellt, die die vom QuizController angebotetenen Endpunkte aufrufen. Der API-Aufruf sieht wie folgt aus:

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

Implementierung der SignalR-Kommunikation im Frontend

Als nächstes folgt der interessante Teil – die Anbindung des Frontends an den SignalR-Hub. Wir erstellen einen neuen Service signalr.service.ts, um darin die Kommunikation mit SignalR zu verwalten. Dazu installieren wir das SignalR Paket, indem wir npm install @microsoft/signalr über die Konsole ausführen. Das Paket stellt uns den HubConnectionBuilder bereit, den wir nutzen, um eine HubConnection zu erzeugen:

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

Die URL ist in diesem Falle die Adresse des Backends, unter der wir zu Beginn in der Startup.cs den QuizHub konfiguriert haben, zum Beispiel https://localhost:44375/hub/. Danach registrieren wir das Frontend auf die Events, auf die die Clients horchen sollen:

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);
  });
}

Zuletzt müssen wir die erstellte HubConnection durch den einfachen Aufruf von start() starten. Der SignalRService kann dann in den Komponenten benutzt werden, die ihre Aktionen auf den Subjects aufrufen, welche wiederum die vom SignalR-Hub kommenden Events an die Komponenten weiterleiten. Somit können die Komponenten auf diese Events reagieren.

Damit ist unsere Beispiel-Anwendung funktionstüchtig. Der Quizmaster kann mit Hilfe des SignalR-Hubs die neue Frage an alle Kandidat*innen schicken und diese können wiederum ihre Tippabgabe an den Quizmaster kommunizieren.

Fazit und Empfehlung

Funktionalität zur Echtzeit-Kommunikation zwischen Clients wird immer häufiger in Webanwendungen benötigt. SignalR bietet hierfür ein solides Framework, welches mit wenig Aufwand in eine bestehende Anwendung eingebunden werden kann, insbesondere bei Nutzung des .NET/Angular Stacks. Durch die Abstraktion der konkreten Technik ist es kompatibel für den Großteil der verwendeten Webbrowser und kann hervorragend für kleine wie auch größere Business-Anwendungen eingesetzt werden.

Bei weiteren Fragen rund um Cross-Client-Kommunikation kontaktieren Sie uns gern.

Joachim Seidler

Segment Manager | Industry
+49 231 9644-465
j.seidler@smf.de


Weiterführende Links