SignalR-JavaScript-Client von ASP.NET Core

Von Rachel Appel

Mit der ASP.NET Core SignalR JavaScript-Clientbibliothek können Entwickler serverseitigen SignalR-Hubcode aufrufen.

Installieren des SignalR-Clientpakets

Die SignalR JavaScript-Clientbibliothek wird als npm-Paket geliefert. In den folgenden Abschnitten werden verschiedene Möglichkeiten zur Installation der Clientbibliothek beschrieben.

Installieren mit npm

Führen Sie die folgenden Befehle der Paket-Manager-Konsole aus:

npm init -y
npm install @microsoft/signalr

npm installiert die Paketinhalte in den Ordner node_modules\@microsoft\signalr\dist\browser. Erstellen Sie den Ordner wwwroot/lib/signalr. Kopieren Sie die Datei signalr.js in den Ordner wwwroot/lib/signalr.

Verweisen Sie auf den SignalR JavaScript-Client im <script>-Element. Beispiel:

<script src="~/lib/signalr/signalr.js"></script>

Verwenden eines Content Delivery Network (CDN)

Um die Clientbibliothek ohne die npm-Voraussetzung zu verwenden, verweisen Sie auf eine im CDN gehostete Kopie der Clientbibliothek. Beispiel:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

Die Clientbibliothek ist in den folgenden CDNs verfügbar:

Installieren mit LibMan

LibMan kann verwendet werden, um bestimmte Dateien der Clientbibliothek aus der im CDN gehosteten Clientbibliothek zu installieren. Fügen Sie z. B. nur die minimierte JavaScript-Datei zum Projekt hinzu. Einzelheiten zu diesem Ansatz finden Sie unter Hinzufügen der SignalR-Clientbibliothek.

Verbinden mit einem Hub

Der folgende Code erstellt und startet eine Verbindung. Beim Namen des Hubs wird die Groß-/Kleinschreibung nicht beachtet:

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

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Ursprungsübergreifende Verbindungen (CORS)

Normalerweise laden Browser Verbindungen aus der gleichen Domäne wie die angeforderte Seite. Es gibt jedoch Fälle, in denen eine Verbindung zu einer anderen Domäne erforderlich ist.

Bei domänenübergreifenden Anforderungenmuss der Clientcode anstelle einer relativen URL eine absolute URL verwenden. Für domänenübergreifende Anforderungen ändern Sie .withUrl("/chathub") in .withUrl("https://{App domain name}/chathub").

Um zu verhindern, dass eine böswillige Website vertrauliche Daten von einer anderen Website liest, sind ursprungsübergreifende Verbindungen standardmäßig deaktiviert. Um eine ursprungsübergreifende Anforderung zuzulassen, aktivieren Sie CORS:

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://example.com")
                .AllowAnyHeader()
                .WithMethods("GET", "POST")
                .AllowCredentials();
        });
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

UseCors muss vor dem Aufruf von MapHub aufgerufen werden.

Aufrufen von Hubmethoden über den Client

JavaScript-Clients rufen öffentliche Methoden auf Hubs über die invoke-Methode von HubConnection auf. Die invoke-Methode akzeptiert Folgendes:

  • Den Namen der Hubmethode.
  • Alle in der Hubmethode definierten Argumente.

In dem folgenden hervorgehobenen Code lautet der Name der Methode auf dem Hub SendMessage. Die an invoke übergebenen zweiten und dritten Argumente entsprechen den Argumenten user und message der Hubmethode:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Das Aufrufen von Hubmethoden durch einen Client wird nur bei Verwendung von Azure Service im Modus SignalRDefault unterstützt. Weitere Informationen finden Sie unter Häufig gestellte Fragen (GitHub-Repository azure-signalr).

Die invoke-Methode gibt eine JavaScript-Promise zurück. Die Promise wird mit dem Rückgabewert (falls vorhanden) aufgelöst, wenn die Methode auf dem Server zurückkehrt. Wenn die Methode auf dem Server einen Fehler auslöst, wird die Promise mit der Fehlermeldung zurückgewiesen. Verwenden Sie async und await oder die then- und catch-Methoden der Promise, um diese Fälle zu behandeln.

JavaScript-Clients können über die send-Methode der HubConnection auch öffentliche Methoden auf Hubs aufrufen. Anders als die Methode invoke wartet die Methode send nicht auf eine Antwort vom Server. Die send-Methode gibt eine JavaScript-Promise zurück. Die Promise wird aufgelöst, wenn die Nachricht an den Server gesendet wurde. Wenn beim Senden der Nachricht ein Fehler auftritt, wird die Promise mit der Fehlermeldung zurückgewiesen. Verwenden Sie async und await oder die then- und catch-Methoden der Promise, um diese Fälle zu behandeln.

Bei der Verwendung von sendwird nicht gewartet, bis der Server die Nachricht empfangen hat. Folglich ist es nicht möglich, Daten oder Fehler vom Server zurückzugeben.

Aufrufen von Clientmethoden über den Hub

Um Nachrichten vom Hub zu empfangen, definieren Sie eine Methode mithilfe der on-Methode der HubConnection.

  • Der Name der JavaScript-Clientmethode.
  • Argumente, die der Hub an die Methode übergibt.

Im folgenden Beispiel lautet der Name der Methode ReceiveMessage. Die Namen der Argumente sind user und message:

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

Der vorangehende Code in connection.on wird ausgeführt, wenn der serverseitige Code ihn mithilfe der Methode SendAsync aufruft:

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

SignalR bestimmt, welche Clientmethode aufgerufen werden soll, indem der Name der Methode und die in SendAsync und connection.on definierten Argumente abgeglichen werden.

Eine bewährte Methode ist der Aufruf der start-Methode für die HubConnection nach on. Dadurch wird sichergestellt, dass die Handler registriert werden, bevor eine Nachricht empfangen wird.

Fehlerbehandlung und Protokollierung

Verwenden Sie console.error, um Fehler in der Konsole des Browsers auszugeben, wenn der Client keine Verbindung herstellen oder keine Nachricht senden kann:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Richten Sie die Ablaufverfolgung auf der Clientseite ein, indem Sie eine Protokollierung und den Typ des Ereignisses übergeben, der beim Herstellen der Verbindung protokolliert werden soll. Meldungen werden mit der angegebenen oder einer höheren Protokollstufe protokolliert. Folgende Protokollstufen sind verfügbar:

  • signalR.LogLevel.Error: Fehlermeldungen. Protokolliert nur Error-Nachrichten.
  • signalR.LogLevel.Warning: Warnmeldungen über mögliche Fehler. Protokolliert Warning-, und Error-Nachrichten.
  • signalR.LogLevel.Information: Statusmeldungen ohne Fehler. Protokolliert Information-, Warning- und Error-Nachrichten.
  • signalR.LogLevel.Trace: Überwachungsnachrichten. Protokolliert alles, einschließlich der zwischen Hub und Client übertragenen Daten.

Verwenden Sie die configureLogging-Methode für HubConnectionBuilder, um die Protokollstufe zu konfigurieren. Die Nachrichten werden in der Browserkonsole protokolliert:

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

Wiederherstellen der Verbindung zwischen Clients

Automatisches Wiederherstellen der Verbindung

Der JavaScript-Client für SignalR kann so konfiguriert werden, dass er mithilfe der Methode WithAutomaticReconnect für HubConnectionBuilder automatisch die Verbindung wiederherstellt. Standardmäßig wird die Verbindung nicht automatisch wiederhergestellt.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Ohne Parameter konfiguriert WithAutomaticReconnect den Client so, dass er 0, 2, 10 bzw. 30 Sekunden wartet, bevor er jeweils versucht, die Verbindung wiederherzustellen. Nach vier Fehlversuchen wird die Verbindung nicht mehr wiederhergestellt.

Bevor Sie einen erneuten Verbindungsversuch starten, gilt Folgendes für die HubConnection:

  • Sie geht in den Zustand HubConnectionState.Reconnecting über und löst ihre onreconnecting-Rückrufe aus.
  • Sie geht nicht in den Zustand Disconnected über und löst ihre onclose-Rückrufe wie eine HubConnection ohne konfiguriertes automatisches Wiederherstellen der Verbindung aus.

Der Ansatz zum Wiederherstellen der Verbindung bietet folgende Möglichkeiten:

  • Warnen der Benutzer, dass die Verbindung unterbrochen wurde.
  • Deaktivieren der Elemente der Benutzeroberfläche.
connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

Wenn der Client die Verbindung innerhalb der ersten vier Versuche erfolgreich wiederherstellt, wechselt die HubConnection zurück in den Zustand Connected und löst ihre onreconnected-Rückrufe aus. Dies bietet die Möglichkeit, den Benutzern mitzuteilen, dass die Verbindung wiederhergestellt wurde.

Da die Verbindung für den Server völlig neu aussieht, wird eine neue connectionId für den onreconnected-Rückruf bereitgestellt.

Der connectionId-Parameter des onreconnected-Rückrufs ist undefiniert, wenn die HubConnection so konfiguriert ist, dass die Aushandlung übersprungen wird.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect konfiguriert die HubConnection nicht für die Wiederholung von anfänglichen Startfehlern, sodass Startfehler manuell behandelt werden müssen:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

Wenn der Client die Verbindung nicht innerhalb der ersten vier Versuche erfolgreich wiederherstellen kann, geht die HubConnection in den Zustand Disconnected über und löst ihre onclose-Rückrufe aus. Dies bietet die Möglichkeit, die Benutzer zu informieren:

  • Die Verbindung wurde dauerhaft unterbrochen.
  • Versuchen Sie, die Seite zu aktualisieren:
connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

Um eine benutzerdefinierte Anzahl von Wiederverbindungsversuchen zu konfigurieren, bevor die Verbindung getrennt wird, oder um den Zeitpunkt der Verbindungswiederherstellung zu ändern, akzeptiert withAutomaticReconnect ein Array von Zahlen, die die Verzögerung in Millisekunden angeben, die gewartet werden soll, ehe die einzelnen Wiederverbindungsversuche gestartet werden.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

Im vorhergehenden Beispiel wird HubConnection so konfiguriert, dass sofort nach Unterbrechung der Verbindung ein neuer Verbindungsversuch unternommen wird. Die Standardkonfiguration wartet auch 0 Sekunden, bis versucht wird, die Verbindung wiederherzustellen.

Wenn der erste Wiederverbindungsversuch fehlschlägt, wird der zweite Wiederverbindungsversuch ebenfalls sofort gestartet, anstatt wie bei Verwendung der Standardkonfiguration zwei Sekunden zu warten.

Wenn der zweite Versuch zum Wiederherstellen der Verbindung fehlerhaft ist, wird der dritte Versuch zum Wiederherstellen der Verbindung nach 10 Sekunden gestartet, was wiederum der Standardkonfiguration entspricht.

Die konfigurierte Zeitsteuerung für die Wiederherstellung der Verbindung weicht vom Standardverhalten ab, indem nach dem dritten fehlerhaften Wiederherstellungsversuch abgebrochen wird, anstatt einen weiteren Wiederherstellungsversuch in weiteren 30 Sekunden zu unternehmen.

Um mehr Kontrolle über Zeitpunkt und Anzahl der Versuche zum automatischen Wiederherstellen der Verbindung zu erhalten, akzeptiert withAutomaticReconnect ein Objekt, das die Schnittstelle IRetryPolicy implementiert, die eine einzelne Methode namens nextRetryDelayInMilliseconds aufweist.

nextRetryDelayInMilliseconds übernimmt ein einzelnes Argument mit dem Typ RetryContext. RetryContext hat drei Eigenschaften: previousRetryCount, elapsedMilliseconds und retryReason, die jeweils eine number, eine number und ein Error sind. Vor dem ersten Wiederverbindungsversuch sind sowohl previousRetryCount als auch elapsedMilliseconds 0, und retryReason ist der Fehler, der den Verbindungsabbruch verursacht hat. Nach jedem fehlgeschlagenen Versuch zum Wiederherstellen der Verbindung wird previousRetryCount um 1 erhöht, elapsedMilliseconds wird aktualisiert, um die bisher für den Versuch zum Wiederherstellen der Verbindung aufgewendete Zeit in Millisekunden wiederzugeben, und retryReason ist der Fehler, der den letzten Versuch zum Wiederherstellen der Verbindung fehlschlagen ließ.

nextRetryDelayInMilliseconds muss entweder eine Zahl zurückgeben, die die Anzahl der Millisekunden angibt, die vor dem nächsten Versuch zum Wiederherstellen der Verbindung gewartet werden soll, oder null, wenn die HubConnection den Versuch zum Wiederherstellen der Verbindung beenden soll.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Alternativ kann ein Code geschrieben werden, der die Verbindung zum Client manuell wiederherstellt, wie im folgenden Abschnitt gezeigt wird.

Manuelles Wiederherstellen der Verbindung

Der folgende Code veranschaulicht einen typischen Versuch zum manuellen Wiederherstellen der Verbindung:

  1. Es wird eine Funktion (in diesem Fall die Funktion start) erstellt, um die Verbindung zu starten.
  2. Rufen Sie die Funktion start im Ereignishandler onclose der Verbindung auf.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

Produktionsimplementierungen verwenden in der Regel ein exponentielles Backoff oder versuchen es eine bestimmte Anzahl von Malen erneut.

Browser: Registerkarte im Ruhezustand

Einige Browser verfügen über ein Feature zum Einfrieren von Registerkarten oder zum Versetzen dieser Registerkarten in den Ruhezustand, um die Nutzung von Computerressourcen für inaktive Registerkarten zu reduzieren. Dies kann dazu führen, dass SignalR-Verbindungen geschlossen werden, was zu einer unerwünschten Benutzererfahrung führen kann. Browser verwenden Heuristiken, um herauszufinden, ob eine Registerkarte in den Ruhezustand versetzt werden sollte. Beispiel:

  • Audiowiedergabe
  • Festlegen einer Websperre
  • Festlegen einer IndexedDB-Sperre
  • Bestehende Verbindung mit einem USB-Gerät
  • Video- oder Audioaufzeichnung
  • Erstellen einer Spiegelung
  • Erfassen eines Fensters oder einer Anzeige

Die Browserheuristik kann sich im Laufe der Zeit ändern und von Browser zu Browser unterschiedlich sein. Überprüfen Sie die Supportmatrix und ermitteln Sie, welche Methode für Ihre Szenarien am besten geeignet ist.

Um zu vermeiden, dass eine App in den Ruhezustand versetzt wird, sollte die App eine der Heuristiken auslösen, die der Browser verwendet.

Das folgende Codebeispiel zeigt, wie Sie eine Websperre verwenden, damit eine Registerkarte aktiv bleibt und ein unerwartetes Schließen der Verbindung vermieden wird.

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

Für das vorangehende Codebeispiel:

  • Websperren sind experimentell. Die bedingte Überprüfung bestätigt, dass der Browser Websperren unterstützt.
  • Die Zusageauflösung lockResolver wird gespeichert, sodass die Sperre freigegeben werden kann, wenn die Registerkarte in den Ruhezustand wechseln darf.
  • Wenn Sie die Verbindung schließen, wird die Sperre durch den Aufruf von lockResolver() freigegeben. Wenn die Sperre freigegeben wird, kann die Registerkarte in den Ruhezustand versetzt werden.

Zusätzliche Ressourcen

Von Rachel Appel

Die ASP.NET Core SignalR-Clientbibliothek für JavaScript ermöglicht es Entwicklern, serverseitigen Hubcode aufzurufen.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

Installieren des SignalR-Clientpakets

Die SignalR JavaScript-Clientbibliothek wird als npm-Paket geliefert. In den folgenden Abschnitten werden verschiedene Möglichkeiten zur Installation der Clientbibliothek beschrieben.

Installieren mit npm

Für Visual Studio führen Sie die folgenden Befehle der Paket-Manager-Konsole aus, während Sie sich im Stammordner befinden. Für Visual Studio Code führen Sie die folgenden Befehle über das integrierte Terminal aus.

npm init -y
npm install @microsoft/signalr

npm installiert die Paketinhalte in den Ordner node_modules\@microsoft\signalr\dist\browser. Erstellen Sie einen neuen Ordner namens signalr unter dem Ordner wwwroot\lib. Kopieren Sie die Datei signalr.js in den Ordner wwwroot\lib\signalr.

Verweisen Sie auf den SignalR JavaScript-Client im <script>-Element. Beispiel:

<script src="~/lib/signalr/signalr.js"></script>

Verwenden eines Content Delivery Network (CDN)

Um die Clientbibliothek ohne die npm-Voraussetzung zu verwenden, verweisen Sie auf eine im CDN gehostete Kopie der Clientbibliothek. Beispiel:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

Die Clientbibliothek ist in den folgenden CDNs verfügbar:

Installieren mit LibMan

LibMan kann verwendet werden, um bestimmte Dateien der Clientbibliothek aus der im CDN gehosteten Clientbibliothek zu installieren. Fügen Sie z. B. nur die minimierte JavaScript-Datei zum Projekt hinzu. Einzelheiten zu diesem Ansatz finden Sie unter Hinzufügen der SignalR-Clientbibliothek.

Verbinden mit einem Hub

Der folgende Code erstellt und startet eine Verbindung. Beim Namen des Hubs wird die Groß-/Kleinschreibung nicht beachtet:

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

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Ursprungsübergreifende Verbindungen

Normalerweise laden Browser Verbindungen aus der gleichen Domäne wie die angeforderte Seite. Es gibt jedoch Fälle, in denen eine Verbindung zu einer anderen Domäne erforderlich ist.

Wichtig

Der Clientcode muss eine absolute URL anstelle einer relativen URL verwenden. Wechseln von .withUrl("/chathub") zu .withUrl("https://myappurl/chathub").

Um zu verhindern, dass eine böswillige Website vertrauliche Daten von einer anderen Website liest, sind ursprungsübergreifende Verbindungen standardmäßig deaktiviert. Um eine ursprungsübergreifende Anforderung zuzulassen, aktivieren Sie sie in der Klasse Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

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

Aufrufen von Hubmethoden über den Client

JavaScript-Clients rufen öffentliche Methoden auf Hubs über die invoke-Methode von HubConnection auf. Die invoke-Methode akzeptiert Folgendes:

  • Den Namen der Hubmethode.
  • Alle in der Hubmethode definierten Argumente.

Im folgenden Beispiel lautet der Name der Methode für den Hub SendMessage. Die an invoke übergebenen zweiten und dritten Argumente entsprechen den Argumenten user und message der Hubmethode:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Hinweis

Das Aufrufen von Hubmethoden durch einen Client wird nur bei Verwendung von Azure SignalR Service im Modus Default unterstützt. Weitere Informationen finden Sie unter Häufig gestellte Fragen (GitHub-Repository azure-signalr).

Die invoke-Methode gibt eine JavaScript-Promise zurück. Die Promise wird mit dem Rückgabewert (falls vorhanden) aufgelöst, wenn die Methode auf dem Server zurückkehrt. Wenn die Methode auf dem Server einen Fehler auslöst, wird die Promise mit der Fehlermeldung zurückgewiesen. Verwenden Sie async und await oder die then- und catch-Methoden der Promise, um diese Fälle zu behandeln.

JavaScript-Clients können über die send-Methode der HubConnection auch öffentliche Methoden auf Hubs aufrufen. Anders als die Methode invoke wartet die Methode send nicht auf eine Antwort vom Server. Die send-Methode gibt eine JavaScript-Promise zurück. Die Promise wird aufgelöst, wenn die Nachricht an den Server gesendet wurde. Wenn beim Senden der Nachricht ein Fehler auftritt, wird die Promise mit der Fehlermeldung zurückgewiesen. Verwenden Sie async und await oder die then- und catch-Methoden der Promise, um diese Fälle zu behandeln.

Hinweis

Bei der Verwendung von send wird nicht gewartet, bis der Server die Nachricht empfangen hat. Folglich ist es nicht möglich, Daten oder Fehler vom Server zurückzugeben.

Aufrufen von Clientmethoden über den Hub

Um Nachrichten vom Hub zu empfangen, definieren Sie eine Methode mithilfe der on-Methode der HubConnection.

  • Der Name der JavaScript-Clientmethode.
  • Argumente, die der Hub an die Methode übergibt.

Im folgenden Beispiel lautet der Name der Methode ReceiveMessage. Die Namen der Argumente sind user und message:

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

Der vorangehende Code in connection.on wird ausgeführt, wenn der serverseitige Code ihn mithilfe der Methode SendAsync aufruft:

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR bestimmt, welche Clientmethode aufgerufen werden soll, indem der Name der Methode und die in SendAsync und connection.on definierten Argumente abgeglichen werden.

Hinweis

Als bewährte Methode rufen Sie die start-Methode auf dem HubConnection nach on auf. So stellen Sie sicher, dass Ihre Handler registriert werden, bevor eine Nachricht empfangen wird.

Fehlerbehandlung und Protokollierung

Verwenden Sie try und catch mit async und await oder der Methode catch der Promise, um clientseitige Fehler zu behandeln. Verwenden Sie console.error, um Fehler in der Konsole des Browsers auszugeben:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Richten Sie die Ablaufverfolgung auf der Clientseite ein, indem Sie eine Protokollierung und den Typ des Ereignisses übergeben, der beim Herstellen der Verbindung protokolliert werden soll. Meldungen werden mit der angegebenen oder einer höheren Protokollstufe protokolliert. Folgende Protokollstufen sind verfügbar:

  • signalR.LogLevel.Error: Fehlermeldungen. Protokolliert nur Error-Nachrichten.
  • signalR.LogLevel.Warning: Warnmeldungen über mögliche Fehler. Protokolliert Warning-, und Error-Nachrichten.
  • signalR.LogLevel.Information: Statusmeldungen ohne Fehler. Protokolliert Information-, Warning- und Error-Nachrichten.
  • signalR.LogLevel.Trace: Überwachungsnachrichten. Protokolliert alles, einschließlich der zwischen Hub und Client übertragenen Daten.

Verwenden Sie die configureLogging-Methode für HubConnectionBuilder, um die Protokollstufe zu konfigurieren. Die Nachrichten werden in der Browserkonsole protokolliert:

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

Wiederherstellen der Verbindung zwischen Clients

Automatisches Wiederherstellen der Verbindung

Der JavaScript-Client für SignalR kann so konfiguriert werden, dass er mithilfe der Methode withAutomaticReconnect für HubConnectionBuilder automatisch die Verbindung wiederherstellt. Standardmäßig wird die Verbindung nicht automatisch wiederhergestellt.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Ohne Parameter wird der Client durch withAutomaticReconnect() so konfiguriert, dass er 0, 2, 10 bzw. 30 Sekunden wartet, ehe er einen erneuten Verbindungsversuch unternimmt, und nach vier fehlgeschlagenen Versuchen beendet wird.

Vor dem Starten der Versuche zum Wiederherstellen der Verbindung geht die HubConnection in den Zustand HubConnectionState.Reconnecting über und löst ihre onreconnecting-Rückrufe aus, anstatt in den Zustand Disconnected überzugehen und ihre onclose-Rückrufe auszulösen wie eine HubConnection ohne konfiguriertes automatisches Wiederherstellen der Verbindung. Dies bietet die Möglichkeit, Benutzer zu warnen, dass die Verbindung unterbrochen wurde, und Benutzeroberflächenelemente zu deaktivieren.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

Wenn der Client die Verbindung innerhalb der ersten vier Versuche erfolgreich wiederherstellt, wechselt die HubConnection wieder in den Zustand Connected und löst ihre onreconnected-Rückrufe aus. Dies bietet die Möglichkeit, den Benutzern mitzuteilen, dass die Verbindung wiederhergestellt wurde.

Da die Verbindung für den Server völlig neu aussieht, wird eine neue connectionId für den onreconnected-Rückruf bereitgestellt.

Warnung

Der connectionId-Parameter des onreconnected-Rückrufs ist undefiniert, wenn der HubConnection-Rückruf so konfiguriert wurde, dass die Aushandlung übersprungen wird.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect() konfiguriert die HubConnection nicht für die Wiederholung von anfänglichen Startfehlern, sodass Startfehler manuell behandelt werden müssen:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

Wenn der Client die Verbindung nicht innerhalb der ersten vier Versuche erfolgreich wiederherstellen kann, geht die HubConnection in den Zustand Disconnected über und löst ihre onclose-Rückrufe aus. Dies bietet die Möglichkeit, den Benutzer darüber zu informieren, dass die Verbindung dauerhaft unterbrochen wurde, und ihm zu empfehlen, die Seite zu aktualisieren:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

Um eine benutzerdefinierte Anzahl von Wiederverbindungsversuchen zu konfigurieren, bevor die Verbindung getrennt wird, oder um den Zeitpunkt der Verbindungswiederherstellung zu ändern, akzeptiert withAutomaticReconnect ein Array von Zahlen, die die Verzögerung in Millisekunden angeben, die gewartet werden soll, ehe die einzelnen Wiederverbindungsversuche gestartet werden.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

Im vorhergehenden Beispiel wird HubConnection so konfiguriert, dass sofort nach Unterbrechung der Verbindung ein neuer Verbindungsversuch unternommen wird. Dies gilt auch für die Standardkonfiguration.

Wenn der erste Wiederverbindungsversuch fehlschlägt, wird der zweite Wiederverbindungsversuch ebenfalls sofort gestartet, anstatt wie in der Standardkonfiguration zwei Sekunden zu warten.

Wenn der zweite Versuch zum Wiederherstellen der Verbindung fehlschlägt, wird der dritte Versuch zum Wiederherstellen der Verbindung nach 10 Sekunden gestartet, was wiederum der Standardkonfiguration entspricht.

Das benutzerdefinierte Verhalten weicht dann wieder vom Standardverhalten ab, indem es nach dem dritten fehlerhaften Versuch zum Wiederherstellen der Verbindung stoppt, anstatt wie in der Standardkonfiguration einen weiteren Versuch zum Wiederherstellen der Verbindung in weiteren 30 Sekunden zu unternehmen.

Wenn Sie noch mehr Kontrolle über Zeitpunkt und Anzahl der automatischen Versuche zum Wiederherstellen der Verbindung wünschen, akzeptiert withAutomaticReconnect ein Objekt, das die IRetryPolicy-Schnittstelle implementiert, die eine einzelne Methode namens nextRetryDelayInMilliseconds aufweist.

nextRetryDelayInMilliseconds übernimmt ein einzelnes Argument mit dem Typ RetryContext. RetryContext hat drei Eigenschaften: previousRetryCount, elapsedMilliseconds und retryReason, die jeweils eine number, eine number und ein Error sind. Vor dem ersten Wiederverbindungsversuch sind sowohl previousRetryCount als auch elapsedMilliseconds 0, und retryReason ist der Fehler, der den Verbindungsabbruch verursacht hat. Nach jedem fehlgeschlagenen Versuch zum Wiederherstellen der Verbindung wird previousRetryCount um 1 erhöht, elapsedMilliseconds wird aktualisiert, um die bisher für den Versuch zum Wiederherstellen der Verbindung aufgewendete Zeit in Millisekunden wiederzugeben, und retryReason ist der Fehler, der den letzten Versuch zum Wiederherstellen der Verbindung fehlschlagen ließ.

nextRetryDelayInMilliseconds muss entweder eine Zahl zurückgeben, die die Anzahl der Millisekunden angibt, die vor dem nächsten Versuch zum Wiederherstellen der Verbindung gewartet werden soll, oder null, wenn die HubConnection den Versuch zum Wiederherstellen der Verbindung beenden soll.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Alternativ können Sie Code schreiben, der die Verbindung zu Ihrem Client manuell wiederherstellt, wie unter Manuelles Wiederherstellen der Verbindung gezeigt.

Manuelles Wiederherstellen der Verbindung

Der folgende Code veranschaulicht einen typischen Versuch zum manuellen Wiederherstellen der Verbindung:

  1. Es wird eine Funktion (in diesem Fall die Funktion start) erstellt, um die Verbindung zu starten.
  2. Rufen Sie die Funktion start im Ereignishandler onclose der Verbindung auf.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

Produktionsimplementierungen verwenden in der Regel ein exponentielles Backoff oder versuchen es eine bestimmte Anzahl von Malen erneut.

Browser: Registerkarte im Ruhezustand

Einige Browser verfügen über ein Feature zum Einfrieren von Registerkarten oder zum Versetzen dieser Registerkarten in den Ruhezustand, um die Nutzung von Computerressourcen für inaktive Registerkarten zu reduzieren. Dies kann dazu führen, dass SignalR-Verbindungen geschlossen werden, was zu einer unerwünschten Benutzererfahrung führen kann. Browser verwenden Heuristiken, um herauszufinden, ob eine Registerkarte in den Ruhezustand versetzt werden sollte. Beispiel:

  • Audiowiedergabe
  • Festlegen einer Websperre
  • Festlegen einer IndexedDB-Sperre
  • Bestehende Verbindung mit einem USB-Gerät
  • Video- oder Audioaufzeichnung
  • Erstellen einer Spiegelung
  • Erfassen eines Fensters oder einer Anzeige

Hinweis

Diese Heuristiken können sich im Laufe der Zeit ändern oder von Browser zu Browser unterschiedlich sein. Überprüfen Sie Ihre Supportmatrix und ermitteln Sie, welche Methode für Ihre Szenarien am besten geeignet ist.

Um zu vermeiden, dass eine App in den Ruhezustand versetzt wird, sollte die App eine der Heuristiken auslösen, die der Browser verwendet.

Das folgende Codebeispiel zeigt, wie Sie eine Websperre verwenden, damit eine Registerkarte aktiv bleibt und ein unerwartetes Schließen der Verbindung vermieden wird.

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

Für das vorangehende Codebeispiel:

  • Websperren sind experimentell. Die bedingte Überprüfung bestätigt, dass der Browser Websperren unterstützt.
  • Die Zusageauflösung (lockResolver) wird gespeichert, sodass die Sperre freigegeben werden kann, wenn die Registerkarte in den Ruhezustand wechseln darf.
  • Wenn Sie die Verbindung schließen, wird die Sperre durch den Aufruf von lockResolver() freigegeben. Wenn die Sperre freigegeben wird, kann die Registerkarte in den Ruhezustand versetzt werden.

Zusätzliche Ressourcen