SignalR API 设计时的注意事项SignalR API design considerations

本文内容

通过Andrew Stanton-nurse

本文提供了用于构建基于 SignalR 的 Api 的指南。

使用自定义对象参数来确保向后兼容性Use custom object parameters to ensure backwards-compatibility

将参数添加到 SignalR 集线器方法 (在客户端或服务器) 是重大更改这意味着较旧的客户端/服务器会在尝试调用不带适当数量的参数的方法时出现错误。但是,将属性添加到自定义对象参数是一项重大更改。这可以用于设计兼容的 Api 时可复原的客户端或服务器上的更改。

例如,考虑如下所示的服务器端 API:

  1. public async Task<string> GetTotalLength(string param1)
  2. {
  3. return param1.Length;
  4. }

JavaScript 客户端调用此方法使用invoke,如下所示:

  1. connection.invoke("GetTotalLength", "value1");

如果以后服务器方法中添加第二个参数,较旧的客户端不会提供此参数值。例如:

  1. public async Task<string> GetTotalLength(string param1, string param2)
  2. {
  3. return param1.Length + param2.Length;
  4. }

当旧客户端尝试调用此方法时,它会收到如下错误:

  1. Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength' due to an error on the server.

在服务器上,将看到一条日志消息如下:

  1. System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.

旧客户端仅发送一个参数,但较新版本的服务器 API 所需的两个参数。使用自定义对象作为参数提供了更大的灵活性。让我们重新设计要使用自定义对象的原始 API:

  1. public class TotalLengthRequest
  2. {
  3. public string Param1 { get; set; }
  4. }
  5. public async Task GetTotalLength(TotalLengthRequest req)
  6. {
  7. return req.Param1.Length;
  8. }

现在,客户端使用对象调用方法:

  1. connection.invoke("GetTotalLength", { param1: "value1" });

无需添加参数,将属性添加到TotalLengthRequest对象:

  1. public class TotalLengthRequest
  2. {
  3. public string Param1 { get; set; }
  4. public string Param2 { get; set; }
  5. }
  6. public async Task GetTotalLength(TotalLengthRequest req)
  7. {
  8. var length = req.Param1.Length;
  9. if (req.Param2 != null)
  10. {
  11. length += req.Param2.Length;
  12. }
  13. return length;
  14. }

当旧客户端发送一个参数,额外Param2属性将保留null你可以检测到通过检查由较旧的客户端发送的消息Param2null并应用默认值。新的客户端可以发送两个参数。

  1. connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });

相同的方法适用于在客户端上定义的方法。可以从服务器端发送自定义对象:

  1. public async Task Broadcast(string message)
  2. {
  3. await Clients.All.SendAsync("ReceiveMessage", new
  4. {
  5. Message = message
  6. });
  7. }

在客户端上,访问Message属性,而无需使用参数:

  1. connection.on("ReceiveMessage", (req) => {
  2. appendMessageToChatWindow(req.message);
  3. });

如果你稍后决定要将消息的发件人添加到负载,请向对象添加属性:

  1. public async Task Broadcast(string message)
  2. {
  3. await Clients.All.SendAsync("ReceiveMessage", new
  4. {
  5. Sender = Context.User.Identity.Name,
  6. Message = message
  7. });
  8. }

较旧的客户端不应为Sender值,因此将其忽略。新的客户端可以通过接受更新,以读取新属性:

  1. connection.on("ReceiveMessage", (req) => {
  2. let message = req.message;
  3. if (req.sender) {
  4. message = req.sender + ": " + message;
  5. }
  6. appendMessageToChatWindow(message);
  7. });

在这种情况下,新的客户端也是不提供的旧服务器的容错Sender值。由于旧的服务器不会提供Sender值时,客户端检查以查看是否存在之前对其进行访问。