本文档介绍了如何对适用于 SignalR 版本 2 的 ASP.NET SignalR 中心 API 的服务器端进行编程,以及演示常见选项的代码示例。
利用 SignalR 中心 API,可以将远程过程调用 (RPC) 从服务器到连接的客户端,以及从客户端到服务器。 在服务器代码中,定义可由客户端调用的方法,并调用在客户端上运行的方法。 在客户端代码中,定义可从服务器调用的方法,并调用在服务器上运行的方法。 SignalR 负责处理所有客户端到服务器管道。
SignalR 还提供名为“持久连接”的较低级别的 API。 有关 SignalR、中心和持久连接的简介,请参阅
SignalR 2 简介
。
本主题中使用的软件版本
Visual Studio 2013
.NET 4.5
SignalR 版本 2
有关 SignalR 早期版本的信息,请参阅
SignalR 旧版本
。
请留下反馈,说明你如何喜欢本教程,以及我们可以在页面底部的评论中改进的内容。 如果你有与本教程不直接相关的问题,可以将其发布到
ASP.NET SignalR 论坛
或
StackOverflow.com
。
本文档包含以下各节:
如何注册 SignalR 中间件
The /signalr URL
配置 SignalR 选项
如何创建和使用中心类
中心对象生存期
JavaScript 客户端中中心名称的 Camel 大小写
强类型中心
如何在中心类中定义客户端可以调用的方法
JavaScript 客户端中方法名称的 Camel 大小写
何时异步执行
从中心方法调用报告进度
如何从 Hub 类调用客户端方法
选择哪些客户端将接收 RPC
方法名称没有编译时验证
不区分大小写的方法名称匹配
如何从中心类管理组成员身份
Add 和 Remove 方法的异步执行
组成员身份持久性
如何处理 Hub 类中的连接生存期事件
调用 OnConnected、OnDisconnected 和 OnReconnected 时
未填充调用方状态
如何从 Context 属性获取有关客户端的信息
如何在客户端和 Hub 类之间传递状态
如何处理中心类中的错误
如何从 Hub 类外部调用客户端方法和管理组
调用客户端方法
管理组成员身份
如何启用跟踪
如何自定义中心管道
有关如何对客户端进行编程的文档,请参阅以下资源:
SignalR 中心 API 指南 - JavaScript 客户端
SignalR 中心 API 指南 - .NET 客户端
SignalR 2 的服务器组件仅在 .NET 4.5 中可用。 运行 .NET 4.0 的服务器必须使用 SignalR v1.x。
如何注册 SignalR 中间件
若要定义客户端将用于连接到中心的路由,请在
MapSignalR
应用程序启动时调用 方法。
MapSignalR
是 类的
OwinExtensions
扩展方法
。 以下示例演示如何使用 OWIN 启动类定义 SignalR 中心路由。
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
public class Startup
public void Configuration(IAppBuilder app)
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
如果要将 SignalR 功能添加到 ASP.NET MVC 应用程序,请确保将 SignalR 路由添加到其他路由之前。 有关详细信息,请参阅教程:使用 SignalR 2 和 MVC 5 入门。
The /signalr URL
默认情况下,客户端将用于连接到中心的路由 URL 为“/signalr”。 (不要将此 URL 与自动生成的 JavaScript 文件的“/signalr/hubs”URL 混淆。有关生成的代理的详细信息,请参阅 SignalR 中心 API 指南 - JavaScript 客户端 - 生成的代理及其用途。)
可能存在使此基 URL 不适用于 SignalR 的异常情况;例如,项目中有一个名为 signalr 的文件夹,并且不想更改名称。 在这种情况下,可以更改基 URL,如以下示例所示, (将示例代码中的“/signalr”替换为所需的 URL) 。
指定 URL 的服务器代码
app.MapSignalR("/signalr", new HubConfiguration());
使用生成的代理 (指定 URL 的 JavaScript 客户端代码)
$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);
指定 URL (的 JavaScript 客户端代码,而不使用生成的代理)
var connection = $.hubConnection("/signalr", { useDefaultPath: false });
指定 URL 的 .NET 客户端代码
var hubConnection = new HubConnection("http://contoso.com/signalr", useDefaultUrl: false);
配置 SignalR 选项
方法的 MapSignalR
重载使你能够指定自定义 URL、自定义依赖项解析程序以及以下选项:
从浏览器客户端使用 CORS 或 JSONP 启用跨域调用。
通常,如果浏览器从 http://contoso.com
加载页面,则 SignalR 连接位于 位于 的同一域中 http://contoso.com/signalr
。 如果 中的 http://contoso.com
页面与 http://fabrikam.com/signalr
建立连接,则表示跨域连接。 出于安全原因,默认禁用跨域连接。 有关详细信息,请参阅 ASP.NET SignalR 中心 API 指南 - JavaScript 客户端 - 如何建立跨域连接。
启用详细的错误消息。
发生错误时,SignalR 的默认行为是向客户端发送通知消息,而不发送有关所发生情况的详细信息。 不建议在生产环境中向客户端发送详细的错误信息,因为恶意用户可能能够在针对应用程序的攻击中使用这些信息。 若要进行故障排除,可以使用此选项暂时启用更详细的错误报告。
禁用自动生成的 JavaScript 代理文件。
默认情况下,会生成一个包含中心类代理的 JavaScript 文件,以响应 URL“/signalr/hubs”。 如果不想使用 JavaScript 代理,或者想要手动生成此文件并在客户端中引用物理文件,则可以使用此选项来禁用代理生成。 有关详细信息,请参阅 SignalR 中心 API 指南 - JavaScript 客户端 - 如何为 SignalR 生成的代理创建物理文件。
以下示例演示如何在调用 MapSignalR
方法时指定 SignalR 连接 URL 和这些选项。 若要指定自定义 URL,请将示例中的“/signalr”替换为要使用的 URL。
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);
如何创建和使用中心类
若要创建中心,请创建派生自 Microsoft.Aspnet.Signalr.Hub 的类。 以下示例演示聊天应用程序的简单 Hub 类。
public class ContosoChatHub : Hub
public async Task NewContosoChatMessage(string name, string message)
await Clients.All.addNewMessageToPage(name, message);
在此示例中,连接的客户端可以调用 NewContosoChatMessage
方法,当该方法调用时,接收的数据将广播到所有连接的客户端。
中心对象生存期
你不会实例化 Hub 类,也不从服务器上的自己的代码调用其方法;所有操作都是由 SignalR 中心管道完成的。 每次需要处理中心操作时(例如客户端连接、断开连接或对服务器进行方法调用时),SignalR 都会创建 Hub 类的新实例。
由于 Hub 类的实例是暂时性的,因此不能使用它们来维护从一个方法调用到下一个方法调用的状态。 每次服务器从客户端接收方法调用时,中心类的新实例都会处理消息。 若要通过多个连接和方法调用来维护状态,请使用其他一些方法,例如数据库、中心类上的静态变量,或者不从 Hub
派生的其他类。 如果使用 Hub 类上的静态变量等方法将数据保存在内存中,则应用域回收时数据将丢失。
如果要从自己的在 Hub 类外部运行的代码向客户端发送消息,则无法通过实例化中心类实例来执行此操作,但可以通过获取对 Hub 类的 SignalR 上下文对象的引用来执行此操作。 有关详细信息,请参阅本主题后面的 如何从中心类外部调用客户端方法和管理组 。
JavaScript 客户端中中心名称的 Camel 大小写
默认情况下,JavaScript 客户端通过使用类名的 camel 大小写版本来引用中心。 SignalR 自动进行此更改,以便 JavaScript 代码符合 JavaScript 约定。 前面的示例在 JavaScript 代码中称为 contosoChatHub
。
public class ContosoChatHub : Hub
使用生成的代理的 JavaScript 客户端
var contosoChatHubProxy = $.connection.contosoChatHub;
如果要为要使用的客户端指定其他名称,请添加 HubName
属性。 使用 HubName
属性时,JavaScript 客户端上不会对 camel 大小写进行名称更改。
[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub
使用生成的代理的 JavaScript 客户端
var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;
可以在应用程序中定义多个中心类。 执行此操作时,连接是共享的,但组是分开的:
如果指定了一个) ,则所有客户端将使用相同的 URL 与服务 (/signalr“或自定义 URL 建立 SignalR 连接,并且该连接将用于服务定义的所有中心。
与在单个类中定义所有中心功能相比,多个中心的性能没有差异。
所有中心都获取相同的 HTTP 请求信息。
由于所有中心共享相同的连接,服务器获取的唯一 HTTP 请求信息是建立 SignalR 连接的原始 HTTP 请求中的信息。 如果使用连接请求通过指定查询字符串将信息从客户端传递到服务器,则无法向不同的中心提供不同的查询字符串。 所有中心都将收到相同的信息。
生成的 JavaScript 代理文件将在一个文件中包含所有中心的代理。
有关 JavaScript 代理的信息,请参阅 SignalR 中心 API 指南 - JavaScript 客户端 - 生成的代理及其用途。
组在中心内定义。
在 SignalR 中,可以定义要广播到已连接客户端子集的命名组。 每个中心单独维护组。 例如,名为“Administrators”的组将包含类 ContosoChatHub
的一组客户端,而同一组名称将引用类 StockTickerHub
的不同客户端集。
Strongly-Typed中心
若要为客户端 (引用的中心方法定义接口,并在中心方法) 上启用 Intellisense,请从 Hub<T>
SignalR 2.1) 中引入的 (派生中心,而不是 Hub
:
public class StrongHub : Hub<IClient>
public async Task Send(string message)
await Clients.All.NewMessage(message);
public interface IClient
Task NewMessage(string message);
如何在中心类中定义客户端可以调用的方法
若要在中心公开要可从客户端调用的方法,请声明一个公共方法,如以下示例所示。
public class ContosoChatHub : Hub
public async Task NewContosoChatMessage(string name, string message)
await Clients.All.addNewMessageToPage(name, message);
public class StockTickerHub : Hub
public IEnumerable<Stock> GetAllStocks()
return _stockTicker.GetAllStocks();
可以指定返回类型和参数(包括复杂类型和数组),如同在任何 C# 方法中一样。 在参数中接收或返回给调用方的任何数据都使用 JSON 在客户端和服务器之间通信,SignalR 会自动处理复杂对象和对象数组的绑定。
JavaScript 客户端中方法名称的 Camel 大小写
默认情况下,JavaScript 客户端通过使用方法名称的 camel 大小写版本来引用中心方法。 SignalR 自动进行此更改,以便 JavaScript 代码符合 JavaScript 约定。
public void NewContosoChatMessage(string userName, string message)
使用生成的代理的 JavaScript 客户端
contosoChatHubProxy.server.newContosoChatMessage(userName, message);
如果要为要使用的客户端指定其他名称,请添加 HubMethodName
属性。
[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)
使用生成的代理的 JavaScript 客户端
contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);
何时异步执行
如果方法将长时间运行或必须执行涉及等待的工作(如数据库查找或 Web 服务调用),则通过返回 Task (代替 void
返回) 或 Task<T> 对象 (代替 T
返回类型) ,使 Hub 方法异步。 从 方法返回 Task
对象时,SignalR 会等待 Task
完成,然后将未包装的结果发送回客户端,因此在客户端中对方法调用进行编码的方式没有区别。
将中心方法异步化可避免在使用 WebSocket 传输时阻止连接。 当中心方法同步执行且传输为 WebSocket 时,将阻止从同一客户端对中心方法的后续调用,直到中心方法完成。
以下示例演示编码为同步或异步运行的相同方法,后跟适用于调用任一版本的 JavaScript 客户端代码。
public IEnumerable<Stock> GetAllStocks()
// Returns data from memory.
return _stockTicker.GetAllStocks();
public async Task<IEnumerable<Stock>> GetAllStocks()
// Returns data from a web service.
var uri = Util.getServiceUri("Stocks");
using (HttpClient httpClient = new HttpClient())
var response = await httpClient.GetAsync(uri);
return (await response.Content.ReadAsAsync<IEnumerable<Stock>>());
使用生成的代理的 JavaScript 客户端
stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
$.each(stocks, function () {
alert(this.Symbol + ' ' + this.Price);
有关如何在 ASP.NET 4.5 中使用异步方法的详细信息,请参阅 在 ASP.NET MVC 4 中使用异步方法。
如果要为方法定义重载,则每个重载中的参数数必须不同。 如果仅通过指定不同的参数类型来区分重载,则中心类将编译,但当客户端尝试调用其中一个重载时,SignalR 服务将在运行时引发异常。
从中心方法调用报告进度
SignalR 2.1 添加了对 .NET 4.5 中引入 的进度报告模式 的支持。 若要实现进度报告,请为客户端可以访问的中心方法定义参数 IProgress<T>
:
public class ProgressHub : Hub
public async Task<string> DoLongRunningThing(IProgress<int> progress)
for (int i = 0; i <= 100; i+=5)
await Task.Delay(200);
progress.Report(i);
return "Job complete!";
编写长时间运行的服务器方法时,请务必使用异步编程模式(如 Async/Await),而不是阻止中心线程。
如何从 Hub 类调用客户端方法
若要从服务器调用客户端方法,请在 Hub 类的方法中使用 Clients
属性。 以下示例演示在所有连接的客户端上调用 addNewMessageToPage
的服务器代码,以及用于在 JavaScript 客户端中定义 方法的客户端代码。
public class ContosoChatHub : Hub
public async Task NewContosoChatMessage(string name, string message)
await Clients.All.addNewMessageToPage(name, message);
调用客户端方法是一个异步操作,并返回 Task
。 使用 await
:
确保发送消息时不会出错。
启用 try-catch 块中的捕获和处理错误。
使用生成的代理的 JavaScript 客户端
contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
// Add the message to the page.
$('#discussion').append('<li><strong>' + htmlEncode(name)
+ '</strong>: ' + htmlEncode(message) + '<li>');
无法从客户端方法获取返回值;语法(如 ) int x = Clients.All.add(1,1)
不起作用。
可以为参数指定复杂类型和数组。 以下示例在方法参数中将复杂类型传递给客户端。
使用复杂对象调用客户端方法的服务器代码
public async Task SendMessage(string name, string message)
await Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
定义复杂对象的服务器代码
public class ContosoChatMessage
public string UserName { get; set; }
public string Message { get; set; }
使用生成的代理的 JavaScript 客户端
var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
console.log(message.UserName + ' ' + message.Message);
选择哪些客户端将接收 RPC
Clients 属性返回 HubConnectionContext 对象,该对象提供多个选项用于指定哪些客户端将接收 RPC:
所有已连接的客户端。
Clients.All.addContosoChatMessageToPage(name, message);
仅调用客户端。
Clients.Caller.addContosoChatMessageToPage(name, message);
除调用客户端之外的所有客户端。
Clients.Others.addContosoChatMessageToPage(name, message);
由连接 ID 标识的特定客户端。
Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
此示例在调用客户端上调用 addContosoChatMessageToPage
,其效果与使用 Clients.Caller
相同。
除指定客户端之外的所有已连接客户端(由连接 ID 标识)。
Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
指定组中所有连接的客户端。
Clients.Group(groupName).addContosoChatMessageToPage(name, message);
指定组中的所有已连接客户端(指定客户端除外),由连接 ID 标识。
Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
指定组中的所有已连接客户端(调用客户端除外)。
Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);
由 userId 标识的特定用户。
Clients.User(userid).addContosoChatMessageToPage(name, message);
默认情况下,这是 IPrincipal.Identity.Name
,但可以通过向 全局主机注册 IUserIdProvider 的实现来更改这一点。
连接 ID 列表中的所有客户端和组。
Clients.Clients(ConnectionIds).broadcastMessage(name, message);
Clients.Groups(GroupIds).broadcastMessage(name, message);
按名称排序的用户。
Clients.Client(username).broadcastMessage(name, message);
SignalR 2.1) 中 (引入的用户名列表。
Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);
方法名称没有编译时验证
指定的方法名称被解释为动态对象,这意味着没有 IntelliSense 或编译时验证。 表达式在运行时计算。 执行方法调用时,SignalR 会将方法名称和参数值发送到客户端,如果客户端具有与名称匹配的方法,则会调用该方法,并将参数值传递给客户端。 如果在客户端上找不到匹配的方法,则不会引发错误。 有关调用客户端方法时 SignalR 在后台传输到客户端的数据格式的信息,请参阅 SignalR 简介。
不区分大小写的方法名称匹配
方法名称匹配不区分大小写。 例如, Clients.All.addContosoChatMessageToPage
在服务器上将在客户端上执行 AddContosoChatMessageToPage
、 addcontosochatmessagetopage
或 addContosoChatMessageToPage
。
调用的方法以异步方式执行。 除非指定后续代码行应等待方法完成,否则在对客户端进行方法调用后的任何代码都将立即执行,而不会等待 SignalR 完成向客户端传输数据。 下面的代码示例演示如何按顺序执行两个客户端方法。
使用 Await (.NET 4.5)
public async Task NewContosoChatMessage(string name, string message)
await Clients.Others.addContosoChatMessageToPage(data);
await Clients.Caller.notifyMessageSent();
如果使用 await
等待客户端方法完成,然后再执行下一行代码,这并不意味着客户端将在执行下一行代码之前实际收到消息。 客户端方法调用的“完成”仅表示 SignalR 已完成发送消息所需的一切。 如果需要验证客户端是否收到了消息,则必须自行对该机制进行编程。 例如,可以在中心上编写 MessageReceived
方法,在 addContosoChatMessageToPage
客户端上的 方法中,可以在执行需要在客户端上执行的任何工作后调用 MessageReceived
。 在 MessageReceived
中心中,可以执行任何工作,具体取决于实际客户端接收和原始方法调用的处理。
如何使用字符串变量作为方法名称
如果要通过使用字符串变量作为方法名称来调用客户端方法,请将 (或 Clients.Others
、 Clients.Caller
等) 转换为 Clients.All
IClientProxy
,然后调用 Invoke (methodName,args...) 。
public async Task NewContosoChatMessage(string name, string message)
string methodToCall = "addContosoChatMessageToPage";
IClientProxy proxy = Clients.All;
await proxy.Invoke(methodToCall, name, message);
如何从中心类管理组成员身份
SignalR 中的组提供了一种将消息广播到已连接客户端的指定子集的方法。 一个组可以有任意数量的客户端,客户端可以是任意数量的组的成员。
若要管理组成员身份,请使用由 Hub 类的 属性提供的 Groups
Add 和 Remove 方法。 以下示例演示 Groups.Add
由客户端代码调用的中心方法中使用的 和 Groups.Remove
方法,后跟调用它们的 JavaScript 客户端代码。
public class ContosoChatHub : Hub
public Task JoinGroup(string groupName)
return Groups.Add(Context.ConnectionId, groupName);
public Task LeaveGroup(string groupName)
return Groups.Remove(Context.ConnectionId, groupName);
使用生成的代理的 JavaScript 客户端
contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);
无需显式创建组。 实际上,当你第一次在调用 Groups.Add
中指定组名称时,会自动创建一个组,当你从该组的成员身份中删除最后一个连接时,该组会被删除。
没有用于获取组成员身份列表或组列表的 API。 SignalR 基于 发布/订阅模型向客户端和组发送消息,服务器不维护组或组成员身份的列表。 这有助于最大程度地提高可伸缩性,因为每当将节点添加到 Web 场时,SignalR 维护的任何状态都必须传播到新节点。
Add 和 Remove 方法的异步执行
Groups.Add
和 Groups.Remove
方法以异步方式执行。 如果要将客户端添加到组,并使用组立即向客户端发送消息,则必须确保 Groups.Add
该方法首先完成。 下面的代码示例演示如何执行此操作。
将客户端添加到组,然后向该客户端发送消息
public async Task JoinGroup(string groupName)
await Groups.Add(Context.ConnectionId, groupName);
await Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
组成员身份持久性
SignalR 跟踪连接,而不是用户,因此,如果希望用户在每次建立连接时都位于同一组中,则必须在用户每次建立新连接时调用 Groups.Add
。
暂时失去连接后,有时 SignalR 可以自动还原连接。 在这种情况下,SignalR 还原的是相同的连接,而不是建立新的连接,因此客户端的组成员身份会自动还原。 即使暂时中断是服务器重新启动或故障的结果,也可能发生这种情况,因为每个客户端的连接状态(包括组成员身份)都舍入到客户端。 如果服务器出现故障并在连接超时之前被新服务器替换,则客户端可以自动重新连接到新服务器,并在其所属的组中重新注册。
当连接断开后、连接超时或客户端断开连接(例如,浏览器导航到新页面 () 时)无法自动还原连接时,组成员身份将丢失。 用户下次连接时将是新连接。 若要在同一用户建立新连接时保持组成员身份,应用程序必须跟踪用户与组之间的关联,并在用户每次建立新连接时还原组成员身份。
有关连接和重新连接的详细信息,请参阅本主题后面的 如何在中心类中处理连接生存期事件 。
使用 SignalR 的应用程序通常必须跟踪用户和连接之间的关联,以便知道哪个用户发送了消息,以及 () 应接收消息的用户。 组用于执行此操作的两种常用模式之一。
单用户组。
可以将用户名指定为组名称,并在用户每次连接或重新连接时将当前连接 ID 添加到组。 向发送到组的用户发送消息。 此方法的一个缺点是组无法为你提供一种方法来了解用户是联机还是脱机。
跟踪用户名与连接 ID 之间的关联。
可以在字典或数据库中存储每个用户名与一个或多个连接 ID 之间的关联,并在用户每次连接或断开连接时更新存储的数据。 若要向用户发送消息,请指定连接 ID。 此方法的缺点是占用更多内存。
如何处理 Hub 类中的连接生存期事件
处理连接生存期事件的典型原因是跟踪用户是否已连接,并跟踪用户名与连接 ID 之间的关联。 若要在客户端连接或断开连接时运行自己的代码,请重写 OnConnected
Hub 类的 、 OnDisconnected
和 OnReconnected
虚拟方法,如以下示例所示。
public class ContosoChatHub : Hub
public override Task OnConnected()
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
public override Task OnDisconnected(bool stopCalled)
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected(stopCalled);
public override Task OnReconnected()
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
调用 OnConnected、OnDisconnected 和 OnReconnected 时
每次浏览器导航到新页面时,都必须建立一个新连接,这意味着 SignalR 将执行 OnDisconnected
方法后 OnConnected
跟 方法。 建立新连接时,SignalR 始终会创建新的连接 ID。
OnReconnected
当 SignalR 可以自动恢复的暂时性连接中断时,例如,在连接超时之前,电缆暂时断开连接并重新连接时,将调用 此方法。OnDisconnected
当客户端断开连接且 SignalR 无法自动重新连接(例如浏览器导航到新页面时)时,将调用 方法。 因此,给定客户端的可能事件序列为 OnConnected
、OnReconnected
、 OnDisconnected
或 OnConnected
。 OnDisconnected
你不会看到给定连接的顺序 OnConnected
、 OnDisconnected
、 OnReconnected
。
OnDisconnected
在某些情况下,不会调用 方法,例如当服务器关闭或应用域被回收时。 当另一台服务器联机或应用域完成回收时,某些客户端可能能够重新连接并触发事件 OnReconnected
。
有关详细信息,请参阅 了解和处理 SignalR 中的连接生存期事件。
未填充调用方状态
连接生存期事件处理程序方法是从服务器调用的,这意味着在客户端上放入 state
对象的任何状态都不会填充 Caller
到服务器上的 属性中。 有关 对象和 Caller
属性的信息state
,请参阅本主题后面的如何在客户端和中心类之间传递状态。
如何从 Context 属性获取有关客户端的信息
若要获取有关客户端的信息,请使用 Context
Hub 类的 属性。 属性 Context
返回一个 HubCallerContext 对象,该对象提供对以下信息的访问权限:
调用客户端的连接 ID。
string connectionID = Context.ConnectionId;
连接 ID 是由 SignalR 分配的 GUID, (无法在自己的代码) 指定值。 每个连接都有一个连接 ID,如果应用程序中有多个中心,则所有中心都使用相同的连接 ID。
HTTP 标头数据。
System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
还可以从 Context.Headers
获取 HTTP 标头。 对同一事物的多次引用的原因是, Context.Headers
先创建该 Context.Request
属性,稍后添加属性,并 Context.Headers
保留该属性以实现向后兼容性。
查询字符串数据。
System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString;
string parameterValue = queryString["parametername"]
还可以从 Context.QueryString
获取查询字符串数据。
在此属性中获取的查询字符串是用于建立 SignalR 连接的 HTTP 请求的查询字符串。 可以通过配置连接在客户端中添加查询字符串参数,这是将客户端数据从客户端传递到服务器的便捷方法。 以下示例演示了使用生成的代理时在 JavaScript 客户端中添加查询字符串的一种方法。
$.connection.hub.qs = { "version" : "1.0" };
有关设置查询字符串参数的详细信息,请参阅 适用于 JavaScript 和 .NET 客户端的 API 指南。
可以在查询字符串数据中找到用于连接的传输方法,以及 SignalR 内部使用的其他一些值:
string transportMethod = queryString["transport"];
的值 transportMethod
将为“webSockets”、“serverSentEvents”、“foreverFrame”或“longPolling”。 请注意,如果在事件处理程序方法中OnConnected
检查此值,在某些情况下,最初可能会获得一个传输值,该值不是连接的最终协商传输方法。 在这种情况下,该方法将引发异常,并在稍后在建立最终传输方法时再次调用。
Cookie。
System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;
还可以从 Context.RequestCookies
获取 Cookie。
用户信息。
System.Security.Principal.IPrincipal user = Context.User;
请求的 HttpContext 对象:
System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();
使用此方法而不是 get HttpContext.Current
获取 HttpContext
SignalR 连接的 对象。
如何在客户端和 Hub 类之间传递状态
客户端代理提供了一个 state
对象,你可以在其中存储要通过每个方法调用传输到服务器的数据。 在服务器上,可以在客户端调用的 Hub 方法中的 属性中访问此数据 Clients.Caller
。 Clients.Caller
不会为连接生存期事件处理程序方法 OnConnected
、 OnDisconnected
和 OnReconnected
填充 属性。
在 对象和 Clients.Caller
属性中创建state
或更新数据可双向工作。 可以更新服务器中的值,这些值将传递回客户端。
以下示例演示 JavaScript 客户端代码,该代码存储状态以使用每个方法调用传输到服务器。
contosoChatHubProxy.state.userName = "Fadi Fakhouri";
contosoChatHubProxy.state.computerName = "fadivm1";
以下示例演示 .NET 客户端中的等效代码。
contosoChatHubProxy["userName"] = "Fadi Fakhouri";
chatHubProxy["computerName"] = "fadivm1";
在 Hub 类中,可以在 属性中 Clients.Caller
访问此数据。 以下示例演示用于检索上一示例中引用的状态的代码。
public async Task NewContosoChatMessage(string data)
string userName = Clients.Caller.userName;
string computerName = Clients.Caller.computerName;
await Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
这种用于保留状态的机制不适用于大量数据,因为放入 或 Clients.Caller
属性的所有内容state
都会随每个方法调用进行舍入。 它对于较小的项(如用户名或计数器)很有用。
在 VB.NET 或强类型中心中,无法通过 Clients.Caller
访问调用方状态对象;而应使用 Clients.CallerState
SignalR 2.1 中引入 () :
在 C 中使用 CallerState#
public async Task NewContosoChatMessage(string data)
string userName = Clients.CallerState.userName;
string computerName = Clients.CallerState.computerName;
await Clients.Others.addContosoChatMessageToPage(data, userName, computerName);
在 Visual Basic 中使用 CallerState
Public Async Function NewContosoChatMessage(message As String) As Task
Dim userName As String = Clients.CallerState.userName
Dim computerName As String = Clients.CallerState.computerName
Await Clients.Others.addContosoChatMessageToPage(message, userName, computerName)
End Sub
如何处理中心类中的错误
若要处理中心类方法中发生的错误,首先请确保“观察”异步操作 (的任何异常,例如使用 await
调用客户端方法) 。 然后使用以下一个或多个方法:
将方法代码包装在 try-catch 块中,并记录异常对象。 出于调试目的,可以将异常发送到客户端,但出于安全原因,不建议向生产中的客户端发送详细信息。
创建处理 OnIncomingError 方法的中心管道模块。 以下示例演示了记录错误的管道模块,后跟 Startup.cs 中将模块注入中心管道的代码。
public class ErrorHandlingPipelineModule : HubPipelineModule
protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
Debug.WriteLine("=> Exception " + exceptionContext.Error.Message);
if (exceptionContext.Error.InnerException != null)
Debug.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message);
base.OnIncomingError(exceptionContext, invokerContext);
public void Configuration(IAppBuilder app)
// Any connection or hub wire up and configuration should go here
GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule());
app.MapSignalR();
HubException
使用 SignalR 2) 中引入的类 (。 可以从任何中心调用引发此错误。 构造 HubError
函数采用字符串消息和对象来存储额外的错误数据。 SignalR 将自动序列化异常并将其发送到客户端,客户端将用于拒绝或失败中心方法调用。
以下代码示例演示如何在中心调用期间引发 HubException
,以及如何处理 JavaScript 和 .NET 客户端上的异常。
演示 HubException 类的服务器代码
public class MyHub : Hub
public async Task Send(string message)
if(message.Contains("<script>"))
throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message });
await Clients.All.send(message);
JavaScript 客户端代码演示对在中心中引发 HubException 的响应
myHub.server.send("<script>")
.fail(function (e) {
if (e.source === 'HubException') {
console.log(e.message + ' : ' + e.data.user);
.NET 客户端代码演示对在中心中引发 HubException 的响应
await myHub.Invoke("Send", "<script>");
catch(HubException ex)
Console.WriteLine(ex.Message);
有关中心管道模块的详细信息,请参阅本主题后面的 如何自定义中心管道 。
如何启用跟踪
若要启用服务器端跟踪,请添加系统。将元素诊断到Web.config文件中,如以下示例所示:
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit https://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="SignalRSamples" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;" />
<add name="SignalRSamplesWithMARS" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;MultipleActiveResultSets=true;" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
<system.diagnostics>
<sources>
<source name="SignalR.SqlMessageBus">
<listeners>
<add name="SignalR-Bus" />
</listeners>
</source>
<source name="SignalR.ServiceBusMessageBus">
<listeners>
<add name="SignalR-Bus" />
</listeners>
</source>
<source name="SignalR.ScaleoutMessageBus">
<listeners>
<add name="SignalR-Bus" />
</listeners>
</source>
<source name="SignalR.Transports.WebSocketTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.ServerSentEventsTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.ForeverFrameTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.LongPollingTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.TransportHeartBeat">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
</sources>
<switches>
<add name="SignalRSwitch" value="Verbose" />
</switches>
<sharedListeners>
<add name="SignalR-Transports"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="transports.log.txt" />
<add name="SignalR-Bus"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="bus.log.txt" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
</configuration>
在 Visual Studio 中运行应用程序时,可以在 “输出 ”窗口中查看日志。
如何从 Hub 类外部调用客户端方法和管理组
若要从与中心类不同的类调用客户端方法,请获取对 Hub 的 SignalR 上下文对象的引用,并使用它来在客户端上调用方法或管理组。
以下示例StockTicker
类获取上下文对象,将其存储在 类的实例中,将类实例存储在静态属性中,并使用单一实例类实例中的上下文在连接到名为 StockTickerHub
的中心的客户端上调用 updateStockPrice
方法。
// For the complete example, go to
// http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-aspnet-signalr
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
// Singleton instance
private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));
private IHubContext _context;
private StockTicker(IHubContext context)
_context = context;
// This method is invoked by a Timer object.
private void UpdateStockPrices(object state)
foreach (var stock in _stocks.Values)
if (TryUpdateStockPrice(stock))
_context.Clients.All.updateStockPrice(stock);
如果需要在长期对象中多次使用上下文,请获取一次引用并保存它,而不是每次都获取它。 获取上下文后,可确保 SignalR 按照与中心方法调用客户端方法相同的顺序向客户端发送消息。 有关演示如何对中心使用 SignalR 上下文的教程,请参阅 使用 ASP.NET SignalR 进行服务器广播。
调用客户端方法
可以指定接收 RPC 的客户端,但选项比从中心类调用时少。 其原因是上下文不与来自客户端的特定调用相关联,因此任何需要了解当前连接 ID 的方法(如 Clients.Others
、 或 Clients.Caller
或 Clients.OthersInGroup
)都不可用。 提供了以下选项:
所有已连接的客户端。
context.Clients.All.addContosoChatMessageToPage(name, message);
由连接 ID 标识的特定客户端。
context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);
所有连接的客户端(指定的客户端除外),由连接 ID 标识。
context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
指定组中所有连接的客户端。
context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);
指定组中的所有已连接客户端(指定客户端除外),由连接 ID 标识。
Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
如果要从中心类中的方法调用非中心类,则可以传入当前连接 ID,并将该 ID 与 、 或 一起使用Clients.Client
来模拟 Clients.Caller
、 Clients.Others
或 Clients.OthersInGroup
。Clients.Group
Clients.AllExcept
在以下示例中 MoveShapeHub
, 类将连接 ID 传递给 Broadcaster
类,以便类 Broadcaster
可以模拟 Clients.Others
。
// For the complete example, see
// http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20
// This sample only shows code that passes connection ID to the non-Hub class,
// in order to simulate Clients.Others.
public class MoveShapeHub : Hub
// Code not shown puts a singleton instance of Broadcaster in this variable.
private Broadcaster _broadcaster;
public void UpdateModel(ShapeModel clientModel)
clientModel.LastUpdatedBy = Context.ConnectionId;
// Update the shape model within our broadcaster
_broadcaster.UpdateShape(clientModel);
public class Broadcaster
public Broadcaster()
_hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
public void UpdateShape(ShapeModel clientModel)
_model = clientModel;
_modelUpdated = true;
// Called by a Timer object.
public void BroadcastShape(object state)
if (_modelUpdated)
_hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
_modelUpdated = false;
管理组成员身份
对于管理组,可以使用与在中心类中相同的选项。
将客户端添加到组
context.Groups.Add(connectionID, groupName);
从组中删除客户端
context.Groups.Remove(connectionID, groupName);
如何自定义中心管道
SignalR 使你能够将自己的代码注入中心管道。 以下示例演示一个自定义中心管道模块,该模块记录从客户端接收的每个传入方法调用和客户端上调用的传出方法调用:
public class LoggingPipelineModule : HubPipelineModule
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name);
return base.OnBeforeIncoming(context);
protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context)
Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub);
return base.OnBeforeOutgoing(context);
Startup.cs 文件中的以下代码将模块注册为在中心管道中运行:
public void Configuration(IAppBuilder app)
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
app.MapSignalR();
可以替代许多不同的方法。 有关完整列表,请参阅 HubPipelineModule 方法。