Hub Api 指南 -服務端(C#)
本文檔提供了SignalR版本2的ASP.NET
Hub Api的服務端編程介紹,並提供了常用選項的代碼示例。原文地址
SignalR Hubs Api 可以使你通過連接在服務端與客戶端之間進行遠程調用(RPCs)。在服務端代碼中定義客戶端調用的方法,並且你可以調用這些方法,讓他們運行在客戶端中。在客戶端代碼定義服務端調用的方法,並且你可以調用這些方法,讓他們運行在服務端中。SingalR為你處理所有客戶端到服務端的管道。
SignalR還提供了一個持久連接的低級API。SignalR、Hub和持久連接的介紹請見Signal R 介紹
本文檔使用的軟件版本
- Visual Studio 2013
- .NET 4.5
- SignalR version 2
文檔版本
關於SignalR更早版本的信息請見SignalR舊版本
怎樣注冊SignalR中間件
當應用程序開啟時調用MapSignalR
方法,定義客戶端將用於連接到Hub的路由。MapSignalR
是Owinextensions
類的一個擴展方法。下面例子顏色如何使用一個OWIN啟動類去定義SignalRHub路由。
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
public class Startup {
public void Configuration(IAppBuilder app) {
// 任何連接或Hub連接和配置都應該在這里
app.MapSignalR();
}
}
}
如果你添加SignalR功能到ASP.NET MVC
應用程序,請確保SignalR路在其他路由之前添加,更多信息請見教程:開始學習signalr2和MVC 5。
/signalr URL
默認情況,客戶端連接到Hub的路由URL都是"/signalr"。(不要將此URL和"/signalr/hubs"URL弄混,后者用於自動生成的JavaScript文件。有關生成代理的更多信息請見SignalR Hubs API Guide - JavaScript客戶端 -生成的代理以及它為您做什么。)
可能有此基礎URL不能用於SignalR的特殊情況;錄入,你有一個名為signalr的文件夾,並且你不想更改它的名稱。在這種情況下,你可以更改該基礎URL,如下例所示。
服務代碼指定URL
app.MapSignalR("/mySignalr",new HubConfiguration());
JavaScript 客戶端指定URL(生成代理)
$.connection.hub.url="/mySignalr";
$.connection.hub.start().done(init);// init 連接開啟后調用方法
JavaScript 客戶端指定URL(沒有生成代理)
var connection = $.hubConnection("/mySignalr", { useDefaultPath: false });
.NET 客戶端指定URL
var hubConnection = new HubConnection("http://contoso.com/mySignalr", useDefaultUrl: false);
SignalR配置選項
MapSignalR
的重載方法可以讓你指定一個自定義的URL、自定義依賴項解析器,和一下選項:
-
在瀏覽器使用CORS或JSONP啟用跨域調用
例如瀏覽器頁面地址是
http://contoso.com/signalr
,SignalR連接在相同域,即http://contoso.com/signalr
。如果來自http://contoso.com
的頁面連接到http://fabrikam.com/signalr
,這就是跨域連接。由於安全原因,跨域連接默認是禁止的。更多消息請見ASP.NET SignalR Hubs API Guide - JavaScript客戶端 - 如何建立跨域連接。 -
啟用明細異常信息
當異常發生時,SignalR默認的處理方式是向客戶端發送一個沒有詳細信息的通知消息。在產品中是不推薦發送詳細的異常信息的,因為一些惡意用戶可能使用這個信息再次攻擊你的應用程序。為了排除故障,你可以使用該選項臨時啟用更有信息的錯誤報告。
-
禁用自動產生JavaScript代理文件
默認情況下,會生成一個JavaScript文件,其中包含Hub類的代理,以響應URL“/signalr/ Hub”。如果你不想使用JavaScript代理,或者你想手動產生這個文件,並在你的客戶端引用該文件,你可以使用該選項去禁用代理的產生。更多詳細信息請見SignalR Hubs API Guide - JavaScript客戶端 - 如何為SignalR生成的代理創建物理文件。
下面示例演示了如何在調用MapSignalR
方法時指定SignalR連接URL和其選項。
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies=false;
app.MapSignalR('mySignalr',hubConfiguration);
如何創建和使用Hub類
要創建Hub類,需要創建一個類繼承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的生命周期
你不能在你的服務器上自己的代碼實中例化Hub類或調用他的方法,所有這些都是通過SignalRHub管道完成的。SignalR在每次需要處理Hub操作(例如客戶端連接、斷開連接或對服務器進行方法調用)時創建Hub類的新實例。
因為Hub類的實例是臨時的,您不能使用它們來維護從一個方法調用到下一個方法調用的狀態。每當服務器接受到客戶端調用的方法時,就實例化一個新的Hub類來處理消息。要通過多個連接和方法調用維護狀態,請使用其他一些方法,如數據庫、Hub
類上的靜態變量或不繼承Hub
的其他類。如果使用Hub
類上的靜態變量等方法將數據持久化到內存中,那么當應用程序域回收時,數據將會丟失。
如果你想從運行在Hub
類之外的自己的代碼向客戶端發送消息,你不能通過實例化一個Hub
類的實例來實現,但是你可以通過你的Hub
類獲取一個SignalR上下文對象引用來實現。更多信息請見該文檔后面如何不同或Hub類調用客戶端方法和管理組。
JavaScript客戶端Hub名稱標示符
默認情況,JavaScript客戶端通過使用類名的駝峰命名規則來引用Hub。SignalR會自動進行此更改,以便JavaScript代碼能夠遵循JavaScript約定。前面的示例在JavaScript代碼中稱為contosoChatHub。
服務端
public class ContosoCatHub:hub
JavaScript客戶端,使用產生的代理
var contosoChatHubProxy = $.connection.contosoChatHub;
如果你想指定一個不同名稱供客戶端使用,請添加HubName
屬性。當你使用HubName
屬性,JavaScript 客戶端上不會更改駱駝大小寫的名稱。
服務端
[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub:Hub
JavaScript客戶端,使用產生的代理
var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;
多個Hub
你可以在一個應用程序中定義多個Hub類。當你這樣做的時候,連接是共享的但是組是分開的:
-
所有客戶端使用一樣的URL("/signalr"或你自己指定的URL)去和服務器創建SignalR連接,並且該連接用於服務器定義的所有HUb。
對於多個Hub,與在單個類中定義所有Hub功能相比,沒有性能差異。
-
所有Hub獲得相同的HTTP請求信息。
由於所有Hub共享相同的連接,因此服務器獲得的唯一 HTTP 請求信息是建立 SignalR 連接的原始 HTTP 請求中的內容。 如果使用連接請求通過指定查詢字符串將信息從客戶端傳遞到服務器,你將不能提供不同的查詢字符到不同的Hub。所有的Hub將接受到相同的信息。
-
產生的JavaScript代理文件將在一個文件里包含所有的Hub
關於JavaScript代理信息請見SignalR Hubs API指南 - JavaScript客戶端 - 生成的代理和它為你做了什么
-
在Hub中定義組
在SignalR中,您可以定義要廣播到連接客戶端子集的組。每個Hub分別維護組。例如一個名為“Administrator”的組將包含類
ContosoChatHub
的一組客戶端,而同樣名稱的組將引用類StockTickerHub
的不同客戶端集。
強類型Hub
為你客戶端可以應用的Hub方法定義一個借口(並且為你的Hub方法啟用智能感知),你的Hub繼承自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);
}
如何在Hub類中定義客戶可以調用的方法
在Hub中公開一個你想沖客戶端調用的方法,請聲明一個公開方法,如下面示例所示:
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 會自動處理復雜對象和對象數組的綁定。
avaScript 客戶端中方法名稱的駱駝大小寫
默認情況下,JavaScript客戶端使用方法名稱的駝峰大小寫版本引用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);
何時異步執行
如果方法會長時間運行或執行一個包含waiting
的工作(例如數據庫查詢或web服務調用),則通過返回Task(無返回參數)或返回Task<T>對象(指定返回對象類型為T)使Hub方法異步。當你從方法返回Task
對象時,SignalR等待Task
執行完成,然后將未包裝的結果返回給客戶端,因此客戶端調用的方法代碼沒有區別。
使用Hub異步方法可以避免在使用WebStocket傳輸模式時阻塞連接。當傳輸模式是WebStocket且Hub方法同步執行時,從同一客戶在Hub上后續方法的調用將被阻塞,直到Hub方法完成。
下面的示例顯示編碼為同步或異步運行的相同方法,然后是適用於調用任一版本的 JavaScript 客戶端代碼。
同步
public IEnumerable<Stock> GetAllStocks()
{
// 從內存中返回數據
return _stockTicker.GetAllStocks();
}
異步
public async Tasl<IEnumerable<Stock>> GetAllStocks()
{
// 從Web服務中返回數據
var uri = Util.getServiceUri("Stocks");
using(HttpClient httpClient=new HttpClient())
{
var respone = await httpClient.GetAsync(uri);
return (await respone.Connect.ReadAsAsync<IEnumerable<Stock>>());
}
}
使用產生的代理的JavaScript客戶端
stockTickerHubProxy.server.GetAllStocks().done(function(stocks){
$.each(stocks,function(){
alter(this.Symbol+' '+this.Price);
});
});
定義重載
如果你想為一個方法定義重載,每個重載方法的參數數量必須不相同。如果你僅僅通過不同的產生類型區分不同的重載方法,Hub類可以編譯,單在客戶端嘗試調用一個重載方法時,服務端會拋出一個運行時異常。
報告Hub方法調用進度
SignalR 2.1 添加了對 .NET 4.5引進的進度報告模式的支持。為了實現進度報告,需要為你客戶端可以訪問的的Hub方法定義一個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 "操作完成!";
}
}
在編寫長時間運行的服務器方法時,重要的是使用異步編程模式(如Async/ Await),而不是阻塞Hub線程。
怎樣從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){
// 添加信息到頁面
$('#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.addContosoChatMessageToPage=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(goupName).addContosoChatMessageToPage(name,message);
-
所有一連接的客戶端,排除一定的組
Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name,message);
-
指定用戶,通過userId識別
Clients.User(userid).addContosoChatMessageToPage(name,message);
默認情況,這是
IPrincipal.Identity.Name
,但是可以通過向全局主機注冊IUserIdProvider的實現來更改。 -
連接ID列表中所有的客戶端和組
Clients.Clients(ConnectionIds).addContosoChatMessageToPage(name,message);
-
一個組列表
Clients.Groups(GroupIds).addContosoChatMessageToPage(name,message);
-
用戶名稱
Clients.Client(UserName).addContosoChatMessageToPage(name,message);
-
一個用戶名稱列表(SignalR2.1引進)
Clients.Users(new string[]{"myUser1","myUser2"}).addContosoChatMessageToPage(name,message);
方法名稱無編譯時驗證
你指定的方法名稱被解析為一個動態對象,這意味着它無法只能感知和編譯時驗證。表達式在運行時求值。當方法在調用執行是,SignalR發送忙伐名稱和參數值到客戶端,並且如果客戶端有名稱匹配的方法,就會調用該方法並將參數值傳遞給方法。如果在客戶端沒有找到匹配的方法,不會產生異常。有關SignalR在調用客戶端方法時在后台傳輸給客戶端的數據格式的信息,請見Signalr介紹。
不區分大小寫的方法名稱匹配
方法名匹配是不區分大小寫的。例如,客戶端的AddContosoChatMessageToPage
、addcontosochatmessagetopage
或addContosoChatMessageToPage
方法在服務器上都可以用 Clients.All.addContosoChatMessageToPage
執行。
異步執行
你可以異步調用方法。在對客戶端進行方法調用之后出現的任何代碼都將立即執行,而無需等待SignalR完成向客戶端傳輸數據,除非您指定后續的代碼行應該等待方法完成。下面的代碼示例演示了如何順序執行兩個客戶端方法。
使用Await(.NET 4.5)
public async Task NewContosoChatMessage(string name,string message) {
await Clients.Others.addContosoChatMessageToPage(data);
await Clents.Caller.notifyMessageSent();
}
如果你使用await
,則在執行下一行代碼之前要等待客戶端方法完成,這並不意味着客戶端會在下一行代碼執行之前收到消息。客戶端方法的調用“完成”僅僅意味着SignalR已完成了發送消息所需的所有操作。如果你需要確認客戶端已接受到消息,你必須自己編寫那個機制。例如你可以在你的Hub編寫MessageReceived
方法,在客戶端上的addContosoChatMessageToPage
方法中,你可以在對客戶端做了任何你需要做的工作之后調用MessageReceived
。在Hub 的MessageReceived
中,你對依賴於原始方法調用的實際客戶端接收和處理做任何工作。
如何使用字符串變量作為方法名
如果你想使用字符串變量作為方法名條用客戶端方法,則強制Clients.All
(或Clients.Others
、等Clients.Caller
)調用IClientProxy
Invoke(方法名稱、args...)。
public async Task NewContosoChatMessage(string name,string message) {
string methodToCall="addContosoChatMessageToPage";
IClientProxy proxy = Clients.All;
await proxy.Invoke(methodToCall,name,message);
}
如何從Hub類管理組成員身份
組在SignalR提供一個將信息廣播到指定的已連接客戶端子集的方法。一個組可以有多個客戶端,一個客戶端可以是多個組的成員。客戶端和組是多對多關系。
管理組成員身份,請使用Hub類Groups
屬性聽的Add和Remove方法。下面示例演示了在了Hub中使用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 farm時,SignalR 維護的任何狀態都必須傳播到新節點。
異步執行添加和刪除方法
Groups.Add
和Groups.Remove
方法異步執行。如果你想添加一個客戶端到組並且使用組立即發送一個消息到客戶端,你必須確保首先完成Groups.Add
方法。下面代碼演示了如何去做。
將客戶端添加到組中,然后向該客戶端發送消息
public async Task JoinGroup(string grouName) {
await Groups.Add(Context.ConnectionId,groupName);
await Clients.Group(groupName).addContosoChatMessageToPage(Context.ConnectionId+"added to group");
}
組成員身份持久性
SignalR跟蹤連接而不是用戶,如果希望用戶每次建立連接時都在同一個組中,則必須在用戶每次建立新連接時都調用Groups.Add
。
在暫時失去連接后,有時SignalR可以自動恢復連接。在這種情況下,SignalR將恢復相同的連接,而不是建立新連接,所以客戶端組成員身份也將自動恢復。即使臨時中斷是由於服務器重新啟動或故障造成的,這也是可能的,因為每個客戶端連接狀態(包括組成員身份)都是往返於客戶端的。如果在連接超時之前,服務器宕機並被新服務器替換,客戶端可以自動重新連接到新服務器,並重新注冊到其所屬的組中。
當連接丟失、超時或客戶端斷開連接(例如當瀏覽器導航到新的頁面)后無法自動恢復連接時,組成員身份將丟失。下一次用戶連接將是一個新連接。 要在同一用戶建立新連接時維護組成員身份,應用程序必須跟蹤用戶和組之間的關聯,並在每次用戶建立新連接時還原組成員身份。
更多關於連接和重新連接的信息,請見本文到稍后內容如何處理Hub類中的連接生命周期事件。
單一用戶組
使用 SignalR 的應用程序通常必須跟蹤用戶和連接之間的關聯,以便知道哪個用戶發送消息以及哪些用戶應該接收。 組用於執行此操作的兩個常用模式之一。
-
單一用戶組
可以將用戶名指定為組名,並且每當用戶連接或再次連接時添加當前連接ID到組。將發送給用戶的信息發送給組。這種方法的缺陷是,組不為您提供一種方法來了解用戶是聯機還是脫機
-
跟蹤用戶名稱和連接ID之間的關聯
你可以存儲每個用戶名和一個或多個連接ID的關系到字典或數據庫,並且在用戶每次連接或端口連接是修改存儲的數據。發送消息到用戶時需要指定連接ID。這種方法的確定是占用太多的內存。
如何處理Hub類中的連接生命周期事件
處理練級什么周期時間的典型原因是跟蹤用戶是否已連接,並且跟蹤用戶名稱和連接ID之間的關系。在客戶端連接或斷開連接時運行你在Hub類中重寫的OnConnected
、OnDisconneected
和OnReconnected
虛擬方法,就像下面示例所示。
public class ContosoChatHub:Hub
{
public override Task OnConnected() {
/*** * 在這里添加你的代碼 * 例如:在聊天應用中,記錄當前連接ID和用戶名之間的關聯。並標記用戶在線。 * 在此方法中的代碼完成后客戶端被告知連接已建立。 * 例如在JavaScript客戶端,執行start().done回調函數 **/
return base.OnConnected();
}
public override Task OnDisconnected() {
/** * 在這里添加你的代碼 * 例如:在聊天應用中,標記用戶已離線,刪除當前連接ID與用戶名之間的關聯。 **/
return base.OnDisconnected();
}
public override Task OnReconnected() {
/** * 在這里添加你的代碼 * 例如:在聊天應用中,在用戶經過一段時間不活動后你可能需要將用戶標記為離線;在這種情況下,將用戶標記為再次在線。 **/
return base.OnReconnected();
}
}
什么時候調用OnConnected、OnDisconnected、OnReconnected
每當瀏覽器導航到新頁面,都必須建立一個新的連接,這意味着SignalR將執行OnDisconnected
方法接着執行Onconnected
方法。SignalR每當新連接建立是都會建立一個新連接ID。
OnReconnected
方法在連接出現臨時中斷(SignalR可以自動從中恢復)時調用,例如在連接超時之前臨時斷開並重新連接一條網線。當客戶端斷開連接並且SignalR不能自動恢復連接,OnDisconnected
方法會被調用。例如瀏覽器導航到新的頁面。因此,給定客戶端可能的事件順序是OnConnected
、OnReconnected
、OnDisconnected
或者OnConnected
、OnDisconnected
。對於給定的連接沒有這種情況OnConnected
、OnDisconnected
、OnReconnected
。
OnDisconnected
方法在某些場景中不會被調用,例如當服務端宕機或App域被回收。當另一個服務器上線或App域完成其回收時,一些客戶端也許可以重新練級並觸發OnReconnected
事件。
更多信息請見理解並處理SignalR連接生命周期時間。
調用者未填充狀態
從服務端調用連接生命周期事件處理方法,這意味着您放在客戶端上state
對象中的任何狀態都不會填充到服務端上的Caller屬性中。有關state
對象和Caler
屬性的更多類容請見本文檔后面的如何在客戶端和Hub類之間傳遞狀態。
如何從Context屬性獲取客戶端的信息
獲取關於客戶端的信息,請使用Hub類的Context
屬性。Context
屬性返回一個HubCallerContext對象,提供以下信息:
-
調用客戶端的連接ID
string connectionID=Context.ConnectionId;
連接ID是一個由SignalR分配的Guid(你不能在你的代碼中指定它的值)。每個連接都有一個連接ID,如果你的應用程序中有多個Hub,所有的Hub都是用相同的連接ID。
-
HTTP頭部數據
System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
你也可以從
Context.Headers
獲取到HTTP headers。重復引用相同東西的原因是先創建了Context.Headers
,然后添加的Context.Headers
屬性,為了向后兼容而保留了Context.Headers
。 -
Query string 數據
System.Collections.Specialized.NameValueCollection queryString =Context.Request.QueryString; string parameterValue=queryString["parametername"];
你也可以從
Context.QueryString
獲取QueryString數據。在此屬性獲得的QueryString一個用於建立SignalR連接的HTTP請求。你可以通過配置連接添加QueryString屬性到客戶端,這是將客戶端數據從客戶端傳遞到服務端的一種方便的方式。下面代碼演示了當你使用產生的代理時,添加QueryString到JavaScript客戶端的一種方法。
$.connection.hub.qs={"version":"1.0"};
更多關於設置QueryString參數的信息,請查閱JavaScript客戶端和.NET客戶端的API指南。
你可以使用在QueryString數據中查到用於連接傳輸的方法,以及SignalR內部使用的一些其他值:
string transportMethod=queryString["transport"];
transportMethod
的值可能是“webStockets”、“serverSentEvents”、“foeverFrame”或者“longPolling”。注意,如果你在OnConnected
事件處理方法中檢查這個值,在某些情況下可能你最初獲得的transport value 不是連接最終協商的transport方法。在這種情況下,OnConnected
事件處理方法會拋出異常,當最終協商transport方法建立時,OnConnected
事件處理方法會再次被調用。 -
Cookies
System.Collections.Generic.IDictionary<string,Cookie> cookies = Context.Request.Cookies;
你可以從
Context.Request.Cookies
獲取到cookies。 -
用戶信息
System.Security.Principal.IPrincipal user = Context.User;
-
請求的HttpContext對象
System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();
使用
Context.Request.GetHttpContext
方法而不是HttpContext.Current
獲取SignalR連接中的HttpContext
對象。
在Hub類和客戶端之間如何傳遞狀態
客戶端代理提供一個state
對象,您可以在其中存儲你想在方法每次調用時傳輸到服務端的數據。在服務端上,在被客戶端調用的Hub方法的Clients.Caller
屬性中訪問這個數據。連接生命周期事件處理方法OnConnected
、OnDisconnected
和OnReconnected
不填充Clients.Caller
屬性。
在狀state
對象和Clients.Caller
屬性中創建或更新數據是雙向的。您可以更新服務器中的值,並將它們傳遞回客戶機。
下面示例演示了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.conputerName;
await Clients.Others.addContosoChatMessageToPage(message,userName,computerName);
}
備注
這種持久化狀態的機制不適用於大量數據,因為在
state
或Clients.Caller
屬性放置的所有內容會隨每個方法調用一起循環。它對於較小的項目(如用戶名或計數器)很有用。
在VB.NET或強類型hub中,調用者狀態對象無法通過Clients.Caller
訪問,而是使用Clients.CallerState
訪問(在SignalR2.1中引入)。
C#使用客戶端狀態
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 使用客戶端狀態
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
在Hub類中如何處理異常
處理Hub類方法發生中的異常,首選確保你“observe”了使用await
異步操作(例如調用客戶端方法)的任何異常。然后下面一個或多個方法:
-
使用將方法代碼包在try-catch代碼塊中,並且記錄異常對象。出於調試目的,您可以將異常發送到客戶端,但是由於安全原因,在產品中發送明詳細的異常信息到客戶端是不推薦的。
-
創建Hub管道模塊來處理OnIncomingError方法。下面代碼演示了一個記錄異常的管道模塊,跟着的代碼在Startup.css中注冊模塊到Hub管道。
public class ErrorHandlingPipelineModeule:HubPipelineModule { protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokeConext) { 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) { // 任何連接或Hub連接和配置都應該在這里 GlobaHost.HubPipeline.AddModule(new ErrorHandlingPipelineModeule()); app.MapSignalR(); }
-
使用
HubException
類(在SignalR2中引進)。這個異常可以從任何Hub調用引發。HubError
構造函數就收一個字符串信息和一個存儲額外異常數據的對象。SignalR將自動序列化異常並將它發送給客戶端,將用於Hub方法調用被拒絕或失敗。下面代碼將演示在Hub調用期間如何拋出
HubException
,和如何在JavaScript客戶和.NET客戶端處理異常。服務端代碼演示HubException類
public class MyHub:Hub { public async Task Send(string message) { if(message.Contains("<script>")) { throw new HubException("此消息將流向客戶端", new {user = Context.User.Identity.Name, message = message}); } await Chillents.All.send(message); } }
JavaScript客戶端代碼,演示在Hub中拋出HubException異常時的響應
myHub.server.send("<script>") .fail(function(e){ if(e.source === 'HubException') { console.log(e.message + ' : ' + e.data.user); } });
.NET 客戶端代碼,演示在Hub中拋出HubException異常時的響應
try { await myHub.Invoke("Send","<script>"); } catch(HubException ex) { Console.WriteLine(ex.Message); }
更多關於Hub管理模塊的信息請見本文檔如何自定義Hub管道。
如何啟用跟蹤
添加system.diagonstice元素到你的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運行應用程序時,可以在Output窗口中查看日志。
如何在Hub類之外調用客戶端方法和管理組
若要從與Hub類不同的類中調用客戶端方法,請獲取一個Hub的SignalR上下文對象的引用,並使用它調用客戶端的方法或管理組。
下面示例中StockTicker
類獲取上下文對象,並將上下文對象存在類的實例中,類實例存儲到靜態屬性,並且在單例實例使用上下文調用已連接到名為StockTickerHub
Hub的客戶端上的updateStockPrice
方法。
/** * 對於完整的示例,請轉到 * http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-aspnet-signalr * 這個示例只顯示了與獲取和使用SignalR上下文相關的代碼。 **/
public class StockTicker {
private readonly static LaZy<StockTicker> _instance = new LaZy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));
private IHubContext _context;
private StockTicker(IHubContext context) {
_context=context;
}
// 被定時器對象調用
private void UpdateStockPrices(object state) {
foreach (var stock in _sotcks.Values)
{
if(TryUpdateStockPrice(stock))
{
_context.Clients.All.updateStockPrice(stock);
}
}
}
}
如果您需要在長生存期對象中多次使用上下文,請獲取一次其引用並保存,而不是每次都去獲取。獲取一次上下以已確保SignalR在你的Hub方法進行客戶單方法調用時以同樣的順序發送消息到客戶端。關於演示如何使用SignalR Hub上下文的教程,請見服務器廣播與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指定的客戶
Context.Clients.Group(groupName,connectionId1,connectionId2).addContosoChatMessageToPage(name,message);
如果你在Hub類的方法調用non-Hub類,你可以傳遞當前連接ID將其用於Clients.Client
、Clients.AllExcept
、 Clients.Group
去模擬Clients.Caller
、Clients.Others
、Clients.OthersInGroup
。在下面示例中,MoveShapeHub
類傳遞連接ID到Broadcaster
類,所以Broadcaster
類可以模擬Clients.Others
。
/** * 完整示例請見 * http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20 * 這個示例只顯示了將連接ID傳遞給non-Hub類的代碼,以模擬Clients.Others。 **/
public class MoveShapeHub : Hub
{
// 未顯示的代碼將一個單例的廣播實例放在這個變量中。
private Brodcaster _broadcaster;
public void UpdateModel(ShapeModel clientModel) {
clientModel.LastUpdateBy = Context.ConnectionId;
// 在我們的廣播器中更新形狀模型
_broadcaster.UpdateShape(clientModel);
}
}
public class Broadcaster {
public Brodcaster() {
_hubContext = GlobaHost.ConnectionManager.GetHubContext<MoveShapeHub>();
}
public void UpdateShape(ShapeModel clientModel) {
_model = clientModel;
_modelUpdated=true;
}
// 被定時器對象調用
public void BrodcastShape(object state) {
if(_modelUpdated)
{
_hubContext.Clients.AllExcept(_model.LastUpdateBy).updateShape(_model);
_modelUpdated=false;
}
}
}
管理組成員身份
對於管理組,你可以使用與Hub類中相同的選項。
-
添加客戶到組
Context.Groups.Add(connectionId,groupName);
-
從組中刪除客戶
Context.Groups.Remove(connectionId,groupName);
如何自定義Hub管道
SignalR允許你注入你自己的代碼到Hub管道。下面示例顯示了一個自定義Hub管道模塊,記錄了每個從客戶端接收到的傳入方法調用和在客戶端調用傳出方法調用:
public class LogginPipelineModule: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.css文件中的代碼注冊模組到Hub管道中運行
public void Configuration(IAppBuilder app) {
GlobaHost.HubPipeline.AddModule(new LoggingPipelineModule());
app.MapSignalR();
}
更多你可以重寫的方法列表,請見HubPipelineModule Methods。