ASP.NET SignalR 中心 API 指南 - 服务器 (SignalR 1.x)

作者 :Patrick FletcherTom Dykstra

警告

本文档不适用于最新版本的 SignalR。 查看 ASP.NET Core SignalR

本文档介绍如何对适用于 SignalR 的 ASP.NET SignalR 中心 API 版本 1.1 的服务器端进行编程,其中代码示例演示了常见选项。

利用 SignalR 中心 API,可以 (RPC 进行远程过程调用,) 从服务器到连接的客户端,从客户端到服务器。 在服务器代码中,定义可由客户端调用的方法,并调用在客户端上运行的方法。 在客户端代码中,定义可从服务器调用的方法,并调用在服务器上运行的方法。 SignalR 负责处理所有客户端到服务器管道。

SignalR 还提供名为“持久连接”的较低级别的 API。 有关 SignalR、中心和持久连接的简介,或有关如何生成完整 SignalR 应用程序的教程,请参阅 SignalR - 入门

概述

本文档包含以下各节:

有关如何对客户端进行编程的文档,请参阅以下资源:

指向 API 参考主题的链接指向 API 的 .NET 4.5 版本。 如果使用 .NET 4,请参阅 API 的 .NET 4 版本主题

如何注册 SignalR 路由并配置 SignalR 选项

若要定义客户端将用于连接到中心的路由,请在应用程序启动时调用 MapHubs 方法。 MapHubs是 类的System.Web.Routing.RouteCollection扩展方法。 以下示例演示如何在 Global.asax 文件中定义 SignalR 中心路由。

using System.Web.Routing;
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
    }
}

如果要将 SignalR 功能添加到 ASP.NET MVC 应用程序,请确保将 SignalR 路由添加到其他路由之前。 有关详细信息,请参阅教程:使用 SignalR 和 MVC 4 入门

The /signalr URL

默认情况下,客户端将用于连接到中心的路由 URL 为“/signalr”。 (不要将此 URL 与自动生成的 JavaScript 文件的“/signalr/hubs”URL 混淆。有关生成的代理的详细信息,请参阅 SignalR 中心 API 指南 - JavaScript 客户端 - 生成的代理及其用途。)

可能存在异常情况,导致此基 URL 不可用于 SignalR;例如,项目中有一个名为 signalr 的文件夹 ,并且不想更改名称。 在这种情况下,可以更改基 URL,如以下示例中所示, (将示例代码中的“/signalr”替换为所需的 URL) 。

指定 URL 的服务器代码

RouteTable.Routes.MapHubs("/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 选项

通过 方法的 MapHubs 重载,可以指定自定义 URL、自定义依赖项解析程序以及以下选项:

  • 启用来自浏览器客户端的跨域调用。

    通常,如果浏览器从 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 生成的代理创建物理文件

以下示例演示如何在调用 MapHubs 方法时指定 SignalR 连接 URL 和这些选项。 若要指定自定义 URL,请将示例中的“/signalr”替换为要使用的 URL。

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableCrossDomain = true;
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs("/signalr", hubConfiguration);

如何创建和使用中心类

若要创建中心,请创建派生自 Microsoft.Aspnet.Signalr.Hub 的类。 以下示例演示了聊天应用程序的简单 Hub 类。

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        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 连接 (/signalr“或自定义 URL,并且该连接用于服务定义的所有中心。

    与在单个类中定义所有中心功能相比,多个中心的性能没有差异。

  • 所有中心都获取相同的 HTTP 请求信息。

    由于所有中心共享同一连接,因此服务器获取的唯一 HTTP 请求信息是建立 SignalR 连接的原始 HTTP 请求中的信息。 如果使用连接请求通过指定查询字符串将信息从客户端传递到服务器,则无法向不同的中心提供不同的查询字符串。 所有中心都会收到相同的信息。

  • 生成的 JavaScript 代理文件将在一个文件中包含所有中心的代理。

    有关 JavaScript 代理的信息,请参阅 SignalR 中心 API 指南 - JavaScript 客户端 - 生成的代理及其功能

  • 组在中心内定义。

    在 SignalR 中,可以定义要广播到已连接客户端子集的命名组。 每个中心单独维护组。 例如,名为“管理员”的组将包含类的一组客户端 ContosoChatHub ,而同一组名称将引用类 StockTickerHub 的不同客户端集。

如何在中心类中定义客户端可以调用的方法

若要在中心公开要从客户端调用的方法,请声明一个公共方法,如以下示例所示。

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}
public class StockTickerHub : Hub
{
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
}

可以指定返回类型和参数(包括复杂类型和数组),如同在任何 C# 方法中一样。 在参数中接收或返回到调用方的任何数据都使用 JSON 在客户端和服务器之间进行通信,SignalR 会自动处理复杂对象和对象数组的绑定。

JavaScript 客户端中方法名称的 Camel 大小写

默认情况下,JavaScript 客户端通过使用方法名称的 camel 大小写版本来引用 Hub 方法。 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 返回类型) ,使中心方法异步。 从 方法返回 Task 对象时,SignalR 会等待 Task 完成,然后将未包装的结果发送回客户端,因此在客户端中编写方法调用的代码方式没有区别。

使用 WebSocket 传输时,将中心方法异步化可避免连接阻塞。 当中心方法同步执行且传输为 WebSocket 时,将阻止从同一客户端对中心方法的后续调用,直到中心方法完成。

以下示例演示编码为以同步或异步方式运行的相同方法,后跟适用于调用任一版本的 JavaScript 客户端代码。

同步

public IEnumerable<Stock> GetAllStocks()
{
    // Returns data from memory.
    return _stockTicker.GetAllStocks();
}

异步 - ASP.NET 4.5

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 服务将在运行时引发异常。

如何从 Hub 类调用客户端方法

若要从服务器调用客户端方法,请在 Clients Hub 类的 方法中使用 属性。 以下示例演示了在所有连接的客户端上调用 addNewMessageToPage 的服务器代码,以及用于在 JavaScript 客户端中定义 方法的客户端代码。

服务器

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}

使用生成的代理的 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 void SendMessage(string name, string message)
{
    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);
    

方法名称没有编译时验证

指定的方法名称被解释为动态对象,这意味着没有 IntelliSense 或编译时验证。 表达式在运行时计算。 执行方法调用时,SignalR 会将方法名称和参数值发送到客户端,如果客户端具有与名称匹配的方法,则会调用该方法,并将参数值传递给客户端。 如果在客户端上找不到匹配的方法,则不会引发错误。 有关调用客户端方法时 SignalR 在后台传输到客户端的数据格式的信息,请参阅 SignalR 简介

不区分大小写的方法名称匹配

方法名称匹配不区分大小写。 例如, Clients.All.addContosoChatMessageToPage 在服务器上将在客户端上执行 AddContosoChatMessageToPageaddcontosochatmessagetopageaddContosoChatMessageToPage

异步执行

调用的方法以异步方式执行。 除非指定后续代码行应等待方法完成,否则在对客户端进行方法调用后的任何代码都将立即执行,而不会等待 SignalR 完成向客户端传输数据。 下面的代码示例演示如何按顺序执行两个客户端方法,一个使用在 .NET 4.5 中运行的代码,一个使用在 .NET 4 中运行的代码。

.NET 4.5 示例

async public Task NewContosoChatMessage(string name, string message)
{
    await Clients.Others.addContosoChatMessageToPage(data);
    Clients.Caller.notifyMessageSent();
}

.NET 4 示例

public void NewContosoChatMessage(string name, string message)
{
    (Clients.Others.addContosoChatMessageToPage(data) as Task).ContinueWith(antecedent =>
        Clients.Caller.notifyMessageSent());
}

如果使用 awaitContinueWith 等待客户端方法完成,然后再执行下一行代码,这并不意味着客户端会在执行下一行代码之前实际接收消息。 客户端方法调用的“完成”仅表示 SignalR 已完成发送消息所需的一切。 如果需要验证客户端是否收到了消息,则必须自行对该机制进行编程。 例如,可以在中心上编写 MessageReceived 方法,在 addContosoChatMessageToPage 客户端上的 方法中,可以在执行需要在客户端上执行的任何工作后调用 MessageReceived 。 在 MessageReceived 中心中,可以执行任何工作,具体取决于实际客户端接收和原始方法调用的处理。

如何使用字符串变量作为方法名称

如果要通过使用字符串变量作为方法名称来调用客户端方法,请将 (或 Clients.OthersClients.Caller等) 转换为 Clients.AllIClientProxy ,然后调用 Invoke (methodName,args...)

public void NewContosoChatMessage(string name, string message)
{
    string methodToCall = "addContosoChatMessageToPage";
    IClientProxy proxy = Clients.All;
    proxy.Invoke(methodToCall, name, message);
}

如何从中心类管理组成员身份

SignalR 中的组提供了一种将消息广播到已连接客户端的指定子集的方法。 一个组可以有任意数量的客户端,客户端可以是任意数量的组的成员。

若要管理组成员身份,请使用由 Hub 类的 属性提供的 GroupsAddRemove 方法。 以下示例演示 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.AddGroups.Remove 方法以异步方式执行。 如果要将客户端添加到组,并使用该组立即向客户端发送消息,则必须首先确保 Groups.Add 方法完成。 以下代码示例演示如何执行此操作,一个是使用在 .NET 4.5 中运行的代码,一个是使用在 .NET 4 中有效的代码

.NET 4.5 示例

async public Task JoinGroup(string groupName)
{
    await Groups.Add(Context.ConnectionId, groupName);
    Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}

.NET 4 示例

public void JoinGroup(string groupName)
{
    (Groups.Add(Context.ConnectionId, groupName) as Task).ContinueWith(antecedent =>
        Clients.Group(groupName).addContosoChatMessageToPage(Context.ConnectionId + " added to group"));
}

组成员身份持久性

SignalR 跟踪连接而不是用户,因此,如果希望用户在每次建立连接时都位于同一组中,则必须在每次用户建立新连接时调用 Groups.Add

暂时断开连接后,SignalR 有时可以自动还原连接。 在这种情况下,SignalR 还原的是相同的连接,而不是建立新的连接,因此客户端的组成员身份会自动还原。 即使临时中断是服务器重新启动或失败的结果,也有可能发生这种情况,因为每个客户端的连接状态(包括组成员身份)都会被舍入到客户端。 如果服务器出现故障,并在连接超时之前被新服务器替换,则客户端可以自动重新连接到新服务器,并重新注册其所属的组。

如果连接丢失后无法自动还原,或者连接超时,或者客户端断开连接 (例如,当浏览器导航到新页面) 时,组成员身份将丢失。 用户下次连接时将是一个新连接。 若要在同一用户建立新连接时保持组成员身份,应用程序必须跟踪用户和组之间的关联,并在用户每次建立新连接时还原组成员身份。

有关连接和重新连接的详细信息,请参阅本主题后面的 如何在 Hub 类中处理连接生存期事件

单用户组

使用 SignalR 的应用程序通常必须跟踪用户与连接之间的关联,以便知道哪个用户已发送消息,以及哪些用户 () 应接收消息。 组用于两种常用模式之一来执行此操作。

  • 单用户组。

    可以将用户名指定为组名称,并在用户每次连接或重新连接时将当前连接 ID 添加到组。 若要向用户发送消息,请向组发送消息。 此方法的缺点是组无法为你提供确定用户是联机还是脱机的方法。

  • 跟踪用户名与连接 ID 之间的关联。

    可以在字典或数据库中存储每个用户名与一个或多个连接 ID 之间的关联,并在用户每次连接或断开连接时更新存储的数据。 若要向用户发送消息,请指定连接 ID。 此方法的缺点是占用更多内存。

如何处理中心类中的连接生存期事件

处理连接生存期事件的典型原因是跟踪用户是否已连接,以及跟踪用户名与连接 ID 之间的关联。 若要在客户端连接或断开连接时运行自己的代码,请重写 OnConnectedHub 类的 、 OnDisconnectedOnReconnected 虚拟方法,如以下示例所示。

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()
    {
        // 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();
    }

    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 无法自动重新连接时(例如,当浏览器导航到新页面时),将调用 方法。 因此,给定客户端的可能事件序列为 OnConnectedOnReconnectedOnDisconnectedOnConnectedOnDisconnected 你不会看到给定连接的序列 OnConnectedOnDisconnectedOnReconnected

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 连接的 对象。

如何在客户端和中心类之间传递状态

客户端代理提供了一个 state 对象,你可以在其中存储要通过每个方法调用传输到服务器的数据。 在服务器上,可以在客户端调用的中心方法的 属性中访问此数据 Clients.Caller 。 不会 Clients.Caller 为连接生存期事件处理程序方法 OnConnectedOnDisconnectedOnReconnected填充 属性。

在 对象和 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 void NewContosoChatMessage(string data)
{
    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}

注意

保存状态的这种机制不适用于大量数据,因为放入 stateClients.Caller 属性的所有内容都会随每个方法调用进行舍入。 它对于较小的项目(如用户名或计数器)很有用。

如何处理中心类中的错误

若要处理 Hub 类方法中发生的错误,请使用以下一种或两种方法:

  • 将方法代码包装在 try-catch 块中,并记录异常对象。 出于调试目的,可以将异常发送到客户端,但出于安全原因,不建议向生产中的客户端发送详细信息。

  • 创建处理 OnIncomingError 方法的中心管道模块。 以下示例演示记录错误的管道模块,后跟 Global.asax 中的代码,该代码将模块注入中心管道。

    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(Exception ex, IHubIncomingInvokerContext context)
        {
            Debug.WriteLine("=> Exception " + ex.Message);
            if (ex.InnerException != null)
            {
                Debug.WriteLine("=> Inner Exception " + ex.InnerException.Message);
            }
            base.OnIncomingError(ex, context);
        }
    }
    
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
        RouteTable.Routes.MapHubs();
    }
    

有关中心管道模块的详细信息,请参阅本主题后面的 如何自定义中心管道

如何启用跟踪

若要启用服务器端跟踪,请添加系统。将 元素诊断到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 类外部调用客户端方法和管理组

若要从与中心类不同的类调用客户端方法,请获取对中心的 SignalR 上下文对象的引用,并使用该引用在客户端上调用方法或管理组。

下面的示例StockTicker类获取上下文对象,将其存储在 类的实例中,将类实例存储在静态属性中,并使用单一实例类实例中的上下文在连接到名为 的StockTickerHub中心的客户端上调用 updateStockPrice 方法。

// For the complete example, go to 
// http://www.asp.net/signalr/overview/signalr-1x/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,但与从 Hub 类调用相比,你拥有的选项更少。 原因是上下文不与来自客户端的特定调用相关联,因此任何需要了解当前连接 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.CallerClients.OthersClients.OthersInGroupClients.GroupClients.AllExcept 在以下示例中 MoveShapeHub , 类将连接 ID 传递给 Broadcaster 类, Broadcaster 以便类可以模拟 Clients.Others

// For the complete example, see
// http://www.asp.net/signalr/overview/getting-started/tutorial-high-frequency-realtime-with-signalr
// 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); 
    } 
}

Global.asax 文件中的以下代码将模块注册为在中心管道中运行:

protected void Application_Start(object sender, EventArgs e) 
{ 
    GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
    RouteTable.Routes.MapHubs();
}

可以替代许多不同的方法。 有关完整列表,请参阅 HubPipelineModule 方法