SignalR API 设计注意事项

作者:Andrew Stanton-Nurse

本文提供有关生成 SignalR基于 API 的指南。

使用自定义对象参数确保向后兼容性

将参数添加到 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;
}

旧客户端发送单个参数时,额外的Param2属性将被保留下来null。 可以通过检查 Param2 中的 null 并应用默认值来检测由旧客户端发送的消息。 新客户端可以发送这两个参数。

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 该值,客户端会检查它是否存在,然后再访问它。

其他资源