ASP.NET SignalR 中心 API 指南 - .NET 客户端 (SignalR 1.x)

作者 :Patrick FletcherTom Dykstra

警告

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

本文档介绍如何在 .NET 客户端(例如 Windows 应用商店 (WinRT) 、WPF、Silverlight 和控制台应用程序)中使用适用于 SignalR 版本 2 的中心 API。

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

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

概述

本文档包含以下各节:

有关示例 .NET 客户端项目,请参阅以下资源:

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

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

客户端设置

安装 Microsoft.AspNet.SignalR.Client NuGet 包 (而不是 Microsoft.AspNet.SignalR 包) 。 此包支持 .NET 4 和 .NET 4.5 的 WinRT、Silverlight、WPF、控制台应用程序和Windows Phone客户端。

如果客户端上的 SignalR 版本与服务器上的版本不同,则 SignalR 通常能够适应差异。 例如,当 SignalR 版本 2.0 发布并在服务器上安装该版本时,服务器将支持安装了 1.1.x 的客户端以及安装了 2.0 的客户端。 如果服务器上的版本与客户端上的版本之间的差异太大,则当客户端尝试建立连接时,SignalR 将 InvalidOperationException 引发异常。 错误消息为“You are using a version of the client that isn't compatible with the server. Client version X.X, server version X.X”。

如何建立连接

必须先创建对象并创建 HubConnection 代理,然后才能建立连接。 若要建立连接,请 StartHubConnection 对象调用 方法。

var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await hubConnection.Start();

注意

对于 JavaScript 客户端,在调用 Start 方法建立连接之前,必须至少注册一个事件处理程序。 对于 .NET 客户端,这不是必需的。 对于 JavaScript 客户端,生成的代理代码会自动为服务器上存在的所有中心创建代理,注册处理程序是指示客户端要使用的中心的方式。 但对于 .NET 客户端,需要手动创建中心代理,因此 SignalR 假定你将使用为其创建代理的任何中心。

示例代码使用默认的“/signalr”URL 连接到 SignalR 服务。 有关如何指定其他基 URL 的信息,请参阅 ASP.NET SignalR 中心 API 指南 - 服务器 - /signalr URL

方法 Start 异步执行。 若要确保后续代码行在建立连接后才执行,请在 await ASP.NET 4.5 异步方法或 .Wait() 同步方法中使用。 请勿在 WinRT 客户端中使用 .Wait()

await connection.Start();
connection.Start().Wait();

HubConnection 类是线程安全的。

来自 Silverlight 客户端的跨域连接

有关如何从 Silverlight 客户端启用跨域连接的信息,请参阅 使服务跨域边界可用

如何配置连接

在建立连接之前,可以指定以下任一选项:

  • 并发连接限制。
  • 查询字符串参数。
  • 传输方法。
  • HTTP 标头。
  • 客户端证书。

如何在 WPF 客户端中设置最大并发连接数

在 WPF 客户端中,可能需要将并发连接的最大数目从其默认值 2 增加。 建议值为 10。

var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
ServicePointManager.DefaultConnectionLimit = 10;
await hubConnection.Start();

有关详细信息,请参阅 ServicePointManager.DefaultConnectionLimit

如何指定查询字符串参数

如果要在客户端连接时将数据发送到服务器,可以将查询字符串参数添加到连接对象。 以下示例演示如何在客户端代码中设置查询字符串参数。

var querystringData = new Dictionary<string, string>();
querystringData.Add("contosochatversion", "1.0");
var connection = new HubConnection("http://contoso.com/", querystringData);

以下示例演示如何在服务器代码中读取查询字符串参数。

public class StockTickerHub : Hub
{
    public override Task OnConnected()
    {
        var version = Context.QueryString["contosochatversion"];
        if (version != "1.0")
        {
            Clients.Caller.notifyWrongVersion();
        }
        return base.OnConnected();
    }
}

如何指定传输方法

在连接过程中,SignalR 客户端通常会与服务器协商以确定服务器和客户端都支持的最佳传输。 如果已知道要使用哪种传输,则可以绕过此协商过程。 若要指定传输方法,请将传输对象传递到 Start 方法。 以下示例演示如何在客户端代码中指定传输方法。

var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await hubConnection.Start(new LongPollingTransport());

Microsoft.AspNet.SignalR.Client.Transports 命名空间包含以下可用于指定传输的类。

ForeverFrame 传输未包含在此列表中,因为它仅由浏览器使用。

有关如何在服务器代码中检查传输方法的信息,请参阅 ASP.NET SignalR Hubs API 指南 - 服务器 - 如何从 Context 属性获取有关客户端的信息。 有关传输和回退的详细信息,请参阅 SignalR 简介 - 传输和回退

如何指定 HTTP 标头

若要设置 HTTP 标头,请在 Headers 连接对象上使用 属性。 以下示例演示如何添加 HTTP 标头。

hubConnection = new hubConnection("http://www.contoso.com/");
connection.Headers.Add("headername", "headervalue");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await connection.Start();

如何指定客户端证书

若要添加客户端证书,请在 AddClientCertificate 连接对象上使用 方法。

hubConnection = new hubConnection("http://www.contoso.com/");
hubConnection.AddClientCertificate(X509Certificate.CreateFromCertFile("MyCert.cer"));
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await connection.Start();

如何创建中心代理

若要在客户端上定义中心可从服务器调用的方法,并调用服务器上的中心上的方法,请通过在连接对象上调用 来 CreateHubProxy 为中心创建代理。 传入的 CreateHubProxy 字符串是中心类的名称,或者属性指定 HubName 的名称(如果已在服务器上使用)。 名称匹配不区分大小写。

服务器上的中心类

public class StockTickerHub : Hub

为 Hub 类创建客户端代理

var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await hubConnection.Start();

如果使用 属性修饰 Hub 类 HubName ,请使用该名称。

服务器上的中心类

[HubName("stockTicker")]
public class StockTickerHub : Hub

为 Hub 类创建客户端代理

var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("stockTicker");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => 
    Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await hubConnection.Start();

代理对象是线程安全的。 事实上,如果使用相同的 hubName调用HubConnection.CreateHubProxy多次,则获得相同的缓存IHubProxy对象。

如何在客户端上定义服务器可以调用的方法

若要定义服务器可以调用的方法,请使用代理的 On 方法来注册事件处理程序。

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

不同的客户端平台对如何编写方法代码来更新 UI 有不同的要求。 显示的示例适用于 WinRT (Windows 应用商店 .NET) 客户端。 本主题后面的单独部分中提供了 WPF、Silverlight 和控制台应用程序示例。

不带参数的方法

如果要处理的方法没有参数,请使用 方法的非泛型重载 On

不带参数调用客户端方法的服务器代码

public class StockTickerHub : Hub
{
    public void NotifyAllClients()
    {
         Clients.All.Notify();
    }
}

从没有参数的服务器调用的方法的 WinRT 客户端代码 (请参阅本主题后面的 WPF 和 Silverlight 示例)

var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHub.On("notify", () =>
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += "Notified!\n";
    }, null)
);
await hubConnection.Start();

具有参数的方法,用于指定参数类型

如果要处理的方法具有参数,请将参数的类型指定为方法的 On 泛型类型。 方法有泛型重载On,使你能够在 Windows Phone 7) 上指定最多 8 个参数 (4 个参数。 在以下示例中,将一个参数发送到 UpdateStockPrice 方法。

使用参数调用客户端方法的服务器代码

public void BroadcastStockPrice(Stock stock)
{
    context.Clients.Others.UpdateStockPrice(stock);
}

用于参数的 Stock 类

public class Stock
{
    public string Symbol { get; set; }
    public decimal Price { get; set; }
}

使用参数从服务器调用的方法的 WinRT 客户端代码 (请参阅本主题后面的 WPF 和 Silverlight 示例)

stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => 
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
    }, null)
);

具有参数的方法,为参数指定动态对象

除了将参数指定为方法的 On 泛型类型,还可以将参数指定为动态对象:

使用参数调用客户端方法的服务器代码

public void BroadcastStockPrice(Stock stock)
{
    context.Clients.Others.UpdateStockPrice(stock);
}

用于参数的 Stock 类

public class Stock
{
    public string Symbol { get; set; }
    public decimal Price { get; set; }
}

使用参数的动态对象从服务器调用的方法的 WinRT 客户端代码 (请参阅本主题后面的 WPF 和 Silverlight 示例)

stockTickerHubProxy.On("UpdateStockPrice", stock => 
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
    }, null)
);

如何删除处理程序

若要删除处理程序,请调用其 Dispose 方法。

从服务器调用的方法的客户端代码

var updateStockPriceHandler = stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => 
    Context.Post(delegate
    {
        textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
    }, null)
);

用于删除处理程序的客户端代码

updateStockPriceHandler.Dispose();

如何从客户端调用服务器方法

若要在服务器上调用方法,请在 Invoke 中心代理上使用 方法。

如果服务器方法没有返回值,请使用 方法的非泛型重载 Invoke

没有返回值的方法的服务器代码

public class StockTickerHub : Hub
{
    public void JoinGroup(string groupName)
    {
        Groups.Add(Context.ConnectionId, groupName); 
    }
}

调用没有返回值的方法的客户端代码

stockTickerHubProxy.Invoke("JoinGroup", hubConnection.ConnectionID, "SignalRChatRoom");

如果服务器方法具有返回值,请将返回类型指定为该方法的 Invoke 泛型类型。

具有返回值并采用复杂类型参数的方法的服务器代码

public IEnumerable<Stock> AddStock(Stock stock)
{
    _stockTicker.AddStock(stock);
    return _stockTicker.GetAllStocks();
}

用于参数和返回值的 Stock 类

public class Stock
{
    public string Symbol { get; set; }
    public decimal Price { get; set; }
}

在 ASP.NET 4.5 异步方法中调用具有返回值并采用复杂类型参数的方法的客户端代码

var stocks = await stockTickerHub.Invoke<IEnumerable<Stock>>("AddStock", new Stock() { Symbol = "MSFT" });
foreach (Stock stock in stocks)
{
    textBox.Text += string.Format("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price);
}

在同步方法中调用具有返回值并采用复杂类型参数的方法的客户端代码

var stocks = stockTickerHub.Invoke<IEnumerable<Stock>>("AddStock", new Stock() { Symbol = "MSFT" }).Result;
foreach (Stock stock in stocks)
{
    textBox.Text += string.Format("Symbol: {0} price: {1}\n", stock.Symbol, stock.Price);
}

方法 Invoke 异步执行并返回 Task 对象。 如果未指定 await.Wait(),将在调用的方法完成执行之前执行下一行代码。

如何处理连接生存期事件

SignalR 提供以下可以处理的连接生存期事件:

  • Received:在连接上收到任何数据时引发。 提供接收的数据。
  • ConnectionSlow:当客户端检测到连接速度缓慢或频繁断开时引发。
  • Reconnecting:基础传输开始重新连接时引发。
  • Reconnected:在基础传输重新连接时引发。
  • StateChanged:连接状态更改时引发。 提供旧状态和新状态。 有关连接状态值的信息,请参阅 ConnectionState 枚举
  • Closed:连接断开连接时引发。

例如,如果要显示非致命错误但导致间歇性连接问题(例如连接速度缓慢或频繁断开)的警告消息,请 ConnectionSlow 处理 事件。

hubConnection.ConnectionSlow += () => Console.WriteLine("Connection problems.");

有关详细信息,请参阅 了解和处理 SignalR 中的连接生存期事件

如何处理错误

如果未在服务器上显式启用详细的错误消息,则 SignalR 在发生错误后返回的异常对象包含有关错误的最少信息。 例如,如果对 的 newContosoChatMessage 调用失败,则错误对象中的错误消息包含“There was an error invoking Hub method 'contosoChatHub.newContosoChatMessage'.”出于安全原因,不建议向生产中的客户端发送详细的错误消息,但如果要启用详细的错误消息以进行故障排除,请在服务器上使用以下代码。

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
RouteTable.Routes.MapHubs(hubConfiguration);

若要处理 SignalR 引发的错误,可以在连接对象上为 Error 事件添加处理程序。

hubConnection.Error += ex => Console.WriteLine("SignalR error: {0}", ex.Message);

若要处理方法调用中的错误,请将代码包装在 try-catch 块中。

try
{
    IEnumerable<Stock> stocks = await stockTickerHub.Invoke<IEnumerable<Stock>>("GetAllStocks");
    foreach (Stock stock in stocks)
    {
        Console.WriteLine("Symbol: {0} price: {1}", stock.Symbol, stock.Price);
    }
}
catch (Exception ex)
{
    Console.WriteLine("Error invoking GetAllStocks: {0}", ex.Message);
}

如何启用客户端日志记录

若要启用客户端日志记录,请在 TraceLevel 连接对象上设置 和 TraceWriter 属性。

var hubConnection = new HubConnection("http://www.contoso.com/");
hubConnection.TraceLevel = TraceLevels.All;
hubConnection.TraceWriter = Console.Out;
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => Console.WriteLine("Stock update for {0} new price {1}", stock.Symbol, stock.Price));
await hubConnection.Start();

服务器可以调用的客户端方法的 WPF、Silverlight 和控制台应用程序代码示例

前面显示的用于定义服务器可以调用的客户端方法的代码示例适用于 WinRT 客户端。 以下示例演示 WPF、Silverlight 和控制台应用程序客户端的等效代码。

不带参数的方法

不带参数从服务器调用的方法的 WPF 客户端代码

stockTickerHub.On<Stock>("notify", () =>
    Dispatcher.InvokeAsync(() =>
        {
            SignalRTextBlock.Text += string.Format("Notified!");
        })
);

从服务器调用的方法的 Silverlight 客户端代码,不带参数

stockTickerHub.On<Stock>("notify", () =>
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += "Notified!";
    }, null)
);

不带参数的服务器调用的方法的控制台应用程序客户端代码

stockTickerHubProxyProxy.On("Notify", () => Console.WriteLine("Notified!"));

具有参数的方法,用于指定参数类型

使用 参数从服务器调用的方法的 WPF 客户端代码

stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => 
    Dispatcher.InvokeAsync(() =>
        {
            textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
        })
);

使用 参数从服务器调用的方法的 Silverlight 客户端代码

stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => 
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
    }, null)
);

使用 参数从服务器调用的方法的控制台应用程序客户端代码

stockTickerHubProxy.On<Stock>("UpdateStockPrice", stock => 
    Console.WriteLine("Symbol {0} Price {1}", stock.Symbol, stock.Price));

具有参数的方法,为参数指定动态对象

使用 参数从服务器调用的方法的 WPF 客户端代码,使用参数的动态对象

stockTickerHubProxy.On("UpdateStockPrice", stock => 
    Dispatcher.InvokeAsync(() =>
        {
            textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
        })
);

从服务器调用的方法的 Silverlight 客户端代码(使用 参数的动态对象)

stockTickerHubProxy.On("UpdateStockPrice", stock => 
    // Context is a reference to SynchronizationContext.Current
    Context.Post(delegate
    {
        textBox.Text += string.Format("Stock update for {0} new price {1}\n", stock.Symbol, stock.Price);
    }, null)
);

使用 参数的动态对象从服务器调用的方法的控制台应用程序客户端代码

stockTickerHubProxy.On("UpdateStockPrice", stock => 
    Console.WriteLine("Symbol {0} Price {1}", stock.Symbol, stock.Price));