SignalR Рекомендации по проектированию API
В этой статье приведены рекомендации по созданию SignalRAPI на основе.
Использование параметров пользовательского объекта для обеспечения обратной совместимости
Добавление параметров в метод концентратора SignalR (на клиенте или сервере) является критическим изменением. Это означает, что старые клиенты и серверы получат ошибки при попытке вызвать метод без соответствующего количества параметров. Однако добавление свойств в параметр пользовательского объекта не является критическим изменением. Это можно использовать для разработки совместимых API, устойчивых к изменениям на клиенте или сервере.
Например, рассмотрим серверный API, как показано ниже:
public int GetTotalLength(string param1)
{
return param1.Length;
}
Клиент JavaScript вызывает этот метод invoke
следующим образом:
connection.invoke("GetTotalLength", "value1");
Если позже добавить второй параметр в метод сервера, старые клиенты не будут предоставлять это значение параметра. Например:
public int GetTotalLength(string param1, string param2)
{
return param1.Length + param2.Length;
}
Когда старый клиент пытается вызвать этот метод, он получит ошибку следующим образом:
Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength' due to an error on the server.
На сервере появится следующее сообщение журнала:
System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.
Старый клиент отправил только один параметр, но более новый API сервера требует двух параметров. Использование пользовательских объектов в качестве параметров обеспечивает большую гибкость. Давайте перепроектируем исходный API для использования пользовательского объекта:
public class TotalLengthRequest
{
public string Param1 { get; set; }
}
public int GetTotalLength(TotalLengthRequest req)
{
return req.Param1.Length;
}
Теперь клиент использует объект для вызова метода:
connection.invoke("GetTotalLength", { param1: "value1" });
Вместо добавления параметра добавьте свойство в TotalLengthRequest
объект:
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;
}
Когда старый клиент отправляет один параметр, будет оставлено null
дополнительное Param2
свойство. Вы можете обнаружить сообщение, отправленное старым клиентом, проверка для null
этого и применив Param2
значение по умолчанию. Новый клиент может отправлять оба параметра.
connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });
Тот же метод работает для методов, определенных на клиенте. Вы можете отправить пользовательский объект на стороне сервера:
public async Task Broadcast(string message)
{
await Clients.All.SendAsync("ReceiveMessage", new
{
Message = message
});
}
На стороне клиента вы обращаетесь к свойству Message
, а не используете параметр:
connection.on("ReceiveMessage", (req) => {
appendMessageToChatWindow(req.message);
});
Если позже вы решите добавить отправителя сообщения в полезные данные, добавьте свойство в объект:
public async Task Broadcast(string message)
{
await Clients.All.SendAsync("ReceiveMessage", new
{
Sender = Context.User.Identity.Name,
Message = message
});
}
Старые клиенты не Sender
ожидают значения, поэтому они будут игнорировать его. Новый клиент может принять его, обновив для чтения нового свойства:
connection.on("ReceiveMessage", (req) => {
let message = req.message;
if (req.sender) {
message = req.sender + ": " + message;
}
appendMessageToChatWindow(message);
});
В этом случае новый клиент также терпим к старому серверу, который не предоставляет Sender
значение. Так как старый сервер не предоставит Sender
значение, клиент проверка, чтобы узнать, существует ли он перед доступом к нему.
Дополнительные ресурсы
ASP.NET Core