SignalR Zagadnienia dotyczące projektowania interfejsu API
Przez Andrew Stanton-Nurse
Ten artykuł zawiera wskazówki dotyczące tworzenia SignalRopartych na interfejsach API.
Używanie niestandardowych parametrów obiektu w celu zapewnienia zgodności z poprzednimi wersjami
Dodanie parametrów do SignalR metody centrum (na kliencie lub serwerze) jest zmianą powodującą niezgodność. Oznacza to, że starsi klienci/serwery będą otrzymywać błędy podczas próby wywołania metody bez odpowiedniej liczby parametrów. Jednak dodanie właściwości do parametru obiektu niestandardowego nie jest zmianą powodującą niezgodność. Może to służyć do projektowania zgodnych interfejsów API, które są odporne na zmiany na kliencie lub serwerze.
Rozważmy na przykład interfejs API po stronie serwera, taki jak poniżej:
public int GetTotalLength(string param1)
{
return param1.Length;
}
Klient JavaScript wywołuje tę metodę przy użyciu następującej metody invoke
:
connection.invoke("GetTotalLength", "value1");
Jeśli później dodasz drugi parametr do metody serwera, starsi klienci nie będą dostarczać tej wartości parametru. Przykład:
public int GetTotalLength(string param1, string param2)
{
return param1.Length + param2.Length;
}
Gdy stary klient spróbuje wywołać tę metodę, zostanie wyświetlony błąd podobny do następującego:
Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength' due to an error on the server.
Na serwerze zostanie wyświetlony komunikat dziennika podobny do następującego:
System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.
Stary klient wysłał tylko jeden parametr, ale nowszy interfejs API serwera wymagał dwóch parametrów. Używanie obiektów niestandardowych jako parametrów zapewnia większą elastyczność. Przeprojektujmy oryginalny interfejs API, aby użyć obiektu niestandardowego:
public class TotalLengthRequest
{
public string Param1 { get; set; }
}
public int GetTotalLength(TotalLengthRequest req)
{
return req.Param1.Length;
}
Teraz klient używa obiektu do wywołania metody :
connection.invoke("GetTotalLength", { param1: "value1" });
Zamiast dodawać parametr, dodaj właściwość do TotalLengthRequest
obiektu:
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;
}
Gdy stary klient wyśle pojedynczy parametr, dodatkowa Param2
właściwość zostanie pozostawiona null
. Możesz wykryć komunikat wysłany przez starszego klienta, sprawdzając Param2
null
i stosując wartość domyślną. Nowy klient może wysłać oba parametry.
connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });
Ta sama technika działa w przypadku metod zdefiniowanych na kliencie. Obiekt niestandardowy można wysłać po stronie serwera:
public async Task Broadcast(string message)
{
await Clients.All.SendAsync("ReceiveMessage", new
{
Message = message
});
}
Po stronie klienta uzyskujesz dostęp do Message
właściwości zamiast używać parametru:
connection.on("ReceiveMessage", (req) => {
appendMessageToChatWindow(req.message);
});
Jeśli później zdecydujesz się dodać nadawcę komunikatu do ładunku, dodaj właściwość do obiektu:
public async Task Broadcast(string message)
{
await Clients.All.SendAsync("ReceiveMessage", new
{
Sender = Context.User.Identity.Name,
Message = message
});
}
Starsi klienci nie będą oczekiwać Sender
wartości, więc zignoruje ją. Nowy klient może go zaakceptować, aktualizując w celu odczytania nowej właściwości:
connection.on("ReceiveMessage", (req) => {
let message = req.message;
if (req.sender) {
message = req.sender + ": " + message;
}
appendMessageToChatWindow(message);
});
W takim przypadku nowy klient jest również odporny na stary serwer, który nie dostarcza Sender
wartości. Ponieważ stary serwer nie dostarczy Sender
wartości, klient sprawdza, czy istnieje przed uzyskaniem do niego dostępu.