SignalR API 設計考量
本文提供建置 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
。 您可以檢查 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
值,因此用戶端會先檢查此值是否存在,再加以存取。