共用方式為


SignalR API 設計考量

作者:Andrew Stanton-Nurse

本文提供建置 SignalR 型 API 的指導方針。

使用自訂物件參數來確保回溯相容性

將參數新增至 SignalR 中樞方法 (在用戶端或伺服器上) 是一項中斷性變更。 這表示較舊的用戶端/伺服器會在未使用適當的參數數目,嘗試叫用方法時收到錯誤。 不過,將屬性新增至自訂物件參數並是中斷性變更。 這可用來設計相容 API,該 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。 您可以檢查 Param2null,並套用預設值,以偵測舊版用戶端所傳送的訊息。 新的用戶端可以傳送這兩個參數。

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 值,因此用戶端會先檢查此值是否存在,再加以存取。

其他資源