Поделиться через


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 значение, клиент проверка, чтобы узнать, существует ли он перед доступом к нему.

Дополнительные ресурсы