Freigeben über


Überlegungen zum Entwurf einer SignalR-API

Von Andrew Stanton-Nurse

Dieser Artikel enthält Anleitungen zum Erstellen von auf SignalR basierenden APIs.

Verwenden benutzerdefinierter Objektparameter zum Sicherstellen von Abwärtskompatibilität

Das Hinzufügen von Parametern zu einer SignalR-Hubmethode (entweder auf dem Client oder Server) ist ein Breaking Change. Dies bedeutet, dass ältere Clients/Server Fehlermeldungen erhalten, wenn sie versuchen, die Methode ohne die entsprechende Anzahl von Parametern aufzurufen. Das Hinzufügen von Eigenschaften zu einem benutzerdefinierten Objektparameter ist jedoch kein Breaking Change. Dies ist möglich, um kompatible APIs zu entwerfen, die gegenüber Änderungen auf dem Client oder Server resilient sind.

Betrachten Sie beispielsweise eine serverseitige API wie die folgende:

public int GetTotalLength(string param1)
{
    return param1.Length;
}

Der JavaScript-Client ruft diese Methode unter Verwendung von invoke wie folgt auf:

connection.invoke("GetTotalLength", "value1");

Wenn Sie der Servermethode später einen zweiten Parameter hinzufügen, stellen ältere Clients diesen Parameterwert nicht bereit. Beispiel:

public int GetTotalLength(string param1, string param2)
{
    return param1.Length + param2.Length;
}

Wenn der alte Client versucht, diese Methode aufzurufen, wird eine Fehlermeldung wie die folgende angezeigt:

Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength' due to an error on the server.

Auf dem Server wird eine Protokollmeldung wie die folgende angezeigt:

System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.

Der alte Client hat nur einen Parameter gesendet, aber die neuere Server-API erforderte zwei Parameter. Durch die Verwendung benutzerdefinierter Objekte als Parameter erhalten Sie mehr Flexibilität. Lassen Sie uns die ursprüngliche API so umgestalten, dass ein benutzerdefiniertes Objekt verwendet wird:

public class TotalLengthRequest
{
    public string Param1 { get; set; }
}

public int GetTotalLength(TotalLengthRequest req)
{
    return req.Param1.Length;
}

Der Client verwendet nun ein Objekt, um die Methode aufzurufen:

connection.invoke("GetTotalLength", { param1: "value1" });

Anstatt einen Parameter hinzuzufügen, fügen Sie dem TotalLengthRequest-Objekt eine Eigenschaft hinzu:

public class TotalLengthRequest
{
    public string Param1 { get; set; }
    public string Param2 { get; set; }
}

public int GetTotalLength(TotalLengthRequest req)
{
    var length = req.Param1.Length;
    if (req.Param2 != null)
    {
        length += req.Param2.Length;
    }
    return length;
}

Wenn der alte Client einen einzelnen Parameter sendet, bleibt die zusätzliche Param2-Eigenschaft als null erhalten. Sie können eine Nachricht erkennen, die von einem älteren Client gesendet wird, indem Sie Param2 auf null überprüfen und einen Standardwert übernehmen. Ein neuer Client kann beide Parameter senden.

connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });

Die gleiche Technik funktioniert für auf dem Client definierte Methoden. Sie können ein benutzerdefiniertes Objekt auf Serverseite senden:

public async Task Broadcast(string message)
{
    await Clients.All.SendAsync("ReceiveMessage", new
    {
        Message = message
    });
}

Auf Clientseite greifen Sie auf die Message-Eigenschaft zu, anstatt einen Parameter zu verwenden:

connection.on("ReceiveMessage", (req) => {
    appendMessageToChatWindow(req.message);
});

Wenn Sie später beschließen, den Absender der Nachricht den Nutzdaten hinzuzufügen, fügen Sie dem Objekt eine Eigenschaft hinzu:

public async Task Broadcast(string message)
{
    await Clients.All.SendAsync("ReceiveMessage", new
    {
        Sender = Context.User.Identity.Name,
        Message = message
    });
}

Die älteren Clients erwarten den Sender-Wert nicht, daher ignorieren sie ihn. Ein neuer Client kann ihn akzeptieren, indem er zum Lesen der neuen Eigenschaft aktualisiert wird:

connection.on("ReceiveMessage", (req) => {
    let message = req.message;
    if (req.sender) {
        message = req.sender + ": " + message;
    }
    appendMessageToChatWindow(message);
});

In diesem Fall ist der neue Client auch tolerant gegenüber einem alten Server, der den Sender-Wert nicht bereitstellt. Da der alte Server den Sender-Wert nicht bereitstellt, überprüft der Client, ob er vorhanden ist, bevor er darauf zugreift.

Zusätzliche Ressourcen