ASP.NET Core SignalR (七):考虑设计向后兼容的SignalR API
此为系列文章,对MSDN ASP.NET Core SignalR 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解。
使用自定义参数对象来确保向后兼容
向SignalR 中心 方法添加参数(要么是服务端方法,要么是客户端方法)是一个重大的变化。这就意味着老的 服务端/客户端在不带有预期个数的参数进行方法调用时候,会得到一些错误。然而,向自定义参数对象 添加属性却并不是一个重大的变化。这可以被用来设计兼容性API,其可以容忍客户端或者服务端发生的变化(参数发生的变化)。
举个例子,考虑像下面这样的一个服务端API:
public async Task<string> GetTotalLength(string param1) { return param1.Length; }
Javascript 客户端将使用 invoke调用这个方法,如下所示:
connection.invoke("GetTotalLength", "value1");
如果之后你给服务方法添加了第二个参数,而老的客户端不会提供这个参数值,比如:
public async Task<string> 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 async Task 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 async Task 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 值,客户端会在访问它之前查看其是否存在。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南