ASP.NET SignalR 中樞 API 指南 - 伺服器 (SignalR 1.x)

作者: Patrick FletcherTom Dykstra

警告

本檔不適用於最新版的 SignalR。 請查看ASP.NET Core SignalR

本檔提供 ASP.NET SignalR Hubs API for SignalR 1.1 版伺服器端的程式設計簡介,以及示範常見選項的程式碼範例。

SignalR Hubs API 可讓您 (RPC 進行遠端程序呼叫,) 從伺服器連線到用戶端,以及從用戶端到伺服器。 在伺服器程式碼中,您會定義可由用戶端呼叫的方法,並呼叫在用戶端上執行的方法。 在用戶端程式代碼中,您會定義可從伺服器呼叫的方法,並呼叫在伺服器上執行的方法。 SignalR 會為您處理所有用戶端對伺服器管線。

SignalR 也提供稱為持續性連線的較低層級 API。 如需 SignalR、Hubs 和 Persistent Connections 的簡介,或示範如何建置完整 SignalR 應用程式的教學課程,請參閱SignalR - 消費者入門

概觀

本文件包含下列章節:

如需如何程式設計用戶端的檔,請參閱下列資源:

API 參考主題的連結是 API 的 .NET 4.5 版本。 如果您使用 .NET 4,請參閱 .NET 4 版本的 API 主題

如何註冊 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 混淆。如需所產生 Proxy 的詳細資訊,請參閱 SignalR Hubs API 指南 - JavaScript 用戶端 - 產生的 Proxy 及其功能。)

在某些情況下,此基底 URL 不適用於 SignalR;例如,您的專案中有一個名為 signalr 的資料夾,而且您不想變更名稱。 在此情況下,您可以變更基底 URL,如下列範例所示, (將範例程式碼中的 「/signalr」 取代為所需的 URL) 。

指定 URL 的伺服器程式碼

RouteTable.Routes.MapHubs("/signalr", new HubConfiguration());

JavaScript 用戶端程式代碼,其會使用產生的 Proxy (來指定 URL)

$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);

JavaScript 用戶端程式代碼,指定 URL (而不產生 Proxy)

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 Hubs API 指南 - JavaScript 用戶端 - 如何建立跨網域連線

  • 啟用詳細的錯誤訊息。

    發生錯誤時,SignalR 的預設行為是將通知訊息傳送給用戶端,而不會詳細說明發生什麼情況。 不建議在生產環境中將詳細的錯誤資訊傳送給用戶端,因為惡意使用者可能會使用對應用程式的攻擊中的資訊。 若要進行疑難排解,您可以使用此選項暫時啟用更詳細的錯誤報表。

  • 停用自動產生的 JavaScript Proxy 檔案。

    根據預設,會產生具有中樞類別 Proxy 的 JavaScript 檔案,以回應 URL 「/signalr/hubs」。 如果您不想使用 JavaScript Proxy,或想要手動產生此檔案,並參考用戶端中的實體檔案,您可以使用此選項來停用 Proxy 產生。 如需詳細資訊,請參閱 SignalR 中樞 API 指南 - JavaScript 用戶端 - 如何為 SignalR 產生的 Proxy 建立實體檔案

下列範例示範如何在呼叫 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 Hubs 管線為您完成的所有作業。 SignalR 會在每次需要處理中樞作業時建立中樞類別的新實例,例如用戶端連線、中斷連線或對伺服器進行方法呼叫時。

因為 Hub 類別的實例是暫時性的,所以您無法使用它們來維護從一個方法呼叫到下一個方法的狀態。 每當伺服器收到來自用戶端的方法呼叫時,中樞類別的新實例就會處理訊息。 若要透過多個連線和方法呼叫維護狀態,請使用一些其他方法,例如資料庫,或 Hub 類別上的靜態變數,或是衍生自 Hub 的不同類別。 如果您在記憶體中保存資料,使用 Hub 類別上的靜態變數之類的方法,當應用程式網域回收時,資料將會遺失。

如果您想要從在 Hub 類別外部執行的程式碼將訊息傳送給用戶端,則無法藉由具現化 Hub 類別實例來執行,但您可以藉由取得 Hub 類別的 SignalR 內容物件的參考來執行。 如需詳細資訊,請參閱本主題稍後 的如何從中樞類別外部呼叫用戶端方法和管理群組

JavaScript 用戶端中中樞名稱的 Camel 大小寫

根據預設,JavaScript 用戶端會使用類別名稱的 Camel 大小寫版本來參考中樞。 SignalR 會自動進行這項變更,讓 JavaScript 程式碼符合 JavaScript 慣例。 上述範例在 JavaScript 程式碼中稱為 contosoChatHub

Server

public class ContosoChatHub : Hub

使用產生的 Proxy 的 JavaScript 用戶端

var contosoChatHubProxy = $.connection.contosoChatHub;

如果您想要為要使用的用戶端指定不同的名稱,請新增 HubName 屬性。 當您使用 HubName 屬性時,JavaScript 用戶端上沒有名稱變更為 Camel 大小寫。

Server

[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub

使用產生的 Proxy 的 JavaScript 用戶端

var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

多個中樞

您可以在應用程式中定義多個中樞類別。 當您這樣做時,會共用連線,但群組是分開的:

  • 如果您指定一個) ,且該服務定義的所有中樞都會使用該連線,所有用戶端都會使用相同的 URL 來建立與服務 (「/signalr」 或自訂 URL 的 SignalR 連線。

    相較于在單一類別中定義所有中樞功能,多個中樞沒有效能差異。

  • 所有中樞都會取得相同的 HTTP 要求資訊。

    由於所有中樞都共用相同的連線,因此伺服器唯一取得的 HTTP 要求資訊就是建立 SignalR 連線的原始 HTTP 要求中。 如果您使用連線要求,藉由指定查詢字串將資訊從用戶端傳遞至伺服器,則無法將不同的查詢字串提供給不同的中樞。 所有中樞都會收到相同的資訊。

  • 產生的 JavaScript Proxy 檔案將包含一個檔案中所有中樞的 Proxy。

    如需 JavaScript Proxy 的相關資訊,請參閱 SignalR Hubs API 指南 - JavaScript 用戶端 - 產生的 Proxy 及其用途

  • 群組是在中樞內定義。

    在 SignalR 中,您可以定義具名群組,以廣播至已連線用戶端的子集。 每個中樞會個別維護群組。 例如,名為 「Administrators」 的群組會包含類別 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 大小寫版本來參考中樞方法。 SignalR 會自動進行這項變更,讓 JavaScript 程式碼符合 JavaScript 慣例。

Server

public void NewContosoChatMessage(string userName, string message)

使用產生的 Proxy 的 JavaScript 用戶端

contosoChatHubProxy.server.newContosoChatMessage($(userName, message);

如果您想要為要使用的用戶端指定不同的名稱,請新增 HubMethodName 屬性。

Server

[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)

使用產生的 Proxy 的 JavaScript 用戶端

contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

非同步執行時機

如果方法長時間執行,或必須執行涉及等候的工作,例如資料庫查閱或 Web 服務呼叫,請傳回Task (void 取代傳回) 或 Task T 物件,讓 Hub 方法以非同步方式傳回) 或Task < T >物件 (取代 T 傳回類型) 。 當您從 方法傳回 Task 物件時,SignalR 會 Task 等候 完成,然後將未包裝的結果傳送回用戶端,因此在用戶端中撰寫方法呼叫的方式沒有任何差異。

讓 Hub 方法成為非同步方法,可避免在使用 WebSocket 傳輸時封鎖連線。 當中樞方法以同步方式執行且傳輸為 WebSocket 時,會封鎖來自相同用戶端中樞上方法的後續調用,直到 Hub 方法完成為止。

下列範例顯示以同步或非同步方式執行的相同方法,後面接著適用于呼叫任一版本的 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>>());
    }
}

使用產生的 Proxy 的 JavaScript 用戶端

stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
    $.each(stocks, function () {
        alert(this.Symbol + ' ' + this.Price);
    });
});

如需如何在 ASP.NET 4.5 中使用非同步方法的詳細資訊,請參閱 在 ASP.NET MVC 4 中使用非同步方法

定義多載

如果您想要定義方法的多載,每個多載中的參數數目必須不同。 如果您只藉由指定不同的參數類型來區分多載,則 Hub 類別會編譯,但當用戶端嘗試呼叫其中一個多載時,SignalR 服務會在執行時間擲回例外狀況。

如何從 Hub 類別呼叫用戶端方法

若要從伺服器呼叫用戶端方法,請使用 Clients Hub 類別中方法中的 屬性。 下列範例顯示呼叫所有已連線用戶端的伺服器程式碼 addNewMessageToPage ,以及在 JavaScript 用戶端中定義 方法的用戶端程式代碼。

Server

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

使用產生的 Proxy 的 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; }
}

使用產生的 Proxy 的 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);
    
  • 以連線識別碼識別的特定用戶端。

    Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
    

    此範例會在呼叫用戶端上呼叫 addContosoChatMessageToPage ,且效果與使用 Clients.Caller 相同。

  • 除了指定的用戶端之外,所有已連線的用戶端都會以連線識別碼識別。

    Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • 指定群組中的所有已連線用戶端。

    Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • 指定群組中的所有已連線用戶端,但指定的用戶端除外,由連線識別碼識別。

    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.CallerIClientProxy ) 轉換成 Clients.All ,然後呼叫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 用戶端程式代碼。

Server

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);
    }
}

使用產生的 Proxy 的 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 的應用程式通常必須追蹤使用者與連線之間的關聯,以瞭解哪些使用者已傳送訊息,以及哪些使用者 () 應該接收訊息。 群組會用於兩個常用的模式之一,以執行此動作。

  • 單一使用者群組。

    您可以將使用者名稱指定為組名,並在每次使用者連接或重新連線時,將目前的連線識別碼新增至群組。 若要將訊息傳送給傳送至群組的使用者。 這個方法的缺點是群組不會提供您瞭解使用者是否在線上或離線的方法。

  • 追蹤使用者名稱和連線識別碼之間的關聯。

    您可以在字典或資料庫中儲存每個使用者名稱和一或多個連線識別碼之間的關聯,並在每次使用者連接或中斷連線時更新儲存的資料。 若要將訊息傳送給使用者,請指定連線識別碼。 這個方法的缺點是需要更多記憶體。

如何處理 Hub 類別中的連線存留期事件

處理連線存留期事件的一般原因是追蹤使用者是否已連線,以及追蹤使用者名稱和連線識別碼之間的關聯。 若要在用戶端連線或中斷連線時執行您自己的程式碼,請覆寫 OnConnected Hub 類別的 、 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 一律會建立新的連線識別碼。

OnReconnected如果連線中斷暫時中斷,SignalR 可以自動從中復原,例如當纜線暫時中斷,並在連線逾時之前重新連線時,就會呼叫 方法。OnDisconnected當用戶端中斷連線且 SignalR 無法自動重新連線時,例如瀏覽器巡覽至新頁面時,就會呼叫 方法。 因此,給定用戶端的可能事件序列為 OnConnectedOnReconnectedOnDisconnectedOnConnectedOnDisconnected 。 您不會看到指定連線的序列 OnConnectedOnDisconnectedOnReconnected

在某些情況下 OnDisconnected 不會呼叫 方法,例如當伺服器關閉或應用程式域回收時。 當另一部伺服器上線或應用程式域完成回收時,某些用戶端可能會重新連線並引發 OnReconnected 事件。

如需詳細資訊,請參閱 瞭解及處理 SignalR 中的連線存留期事件

未填入呼叫端狀態

從伺服器呼叫連線存留期事件處理常式方法,這表示您在用戶端上放置 state 物件的任何狀態都不會填入 Caller 伺服器上的 屬性中。 如需物件和 Caller 屬性的相關資訊 state ,請參閱本主題稍後的如何在用戶端與中樞類別之間傳遞狀態

如何從 CoNtext 屬性取得用戶端的相關資訊

若要取得用戶端的相關資訊,請使用 Context Hub 類別的 屬性。 屬性 Context 會傳回 HubCallerCoNtext 物件,提供下列資訊的存取權:

  • 呼叫用戶端的連線識別碼。

    string connectionID = Context.ConnectionId;
    

    連線識別碼是由 SignalR 指派的 GUID, (您無法在自己的程式碼中指定值) 。 每個連線都有一個連線識別碼,而且如果您的應用程式中有多個中樞,則所有中樞都會使用相同的連線識別碼。

  • 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 要求搭配使用的查詢字串。 您可以藉由設定連線,在用戶端中新增查詢字串參數,這是將用戶端相關資料從用戶端傳遞至伺服器的便利方式。 下列範例顯示當您使用產生的 Proxy 時,在 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();
    

    使用這個方法,而不是取得 HttpContext.CurrentHttpContext SignalR 連線的物件。

如何在用戶端與中樞類別之間傳遞狀態

用戶端 Proxy 提供 state 物件,您可以在其中儲存您想要透過每個方法呼叫傳送至伺服器的資料。 在伺服器上,您可以在用戶端所呼叫中樞方法的 Clients.Caller 屬性中存取此資料。 未 Clients.Caller 針對連接存留期事件處理常式方法 OnConnectedOnDisconnectedOnReconnected 填入 屬性。

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

注意

保存狀態的這個機制不適用於大量資料,因為您在 或 Clients.Caller 屬性中 state 放入的所有專案都會使用每個方法調用來四捨五入。 這適用于較小的專案,例如使用者名稱或計數器。

如何處理中樞類別中的錯誤

若要處理中樞類別方法中發生的錯誤,請使用下列其中一個或兩種方法:

  • 將方法程式碼包裝在 try-catch 區塊中,並記錄例外狀況物件。 基於偵錯目的,您可以將例外狀況傳送至用戶端,但基於安全性考慮,不建議將詳細資訊傳送至生產中的用戶端。

  • 建立處理 OnIncomingError 方法的中樞管線模組。 下列範例顯示記錄錯誤的管線模組,後面接著將模組插入 Hubs 管線中的 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();
    }
    

如需中樞管線模組的詳細資訊,請參閱本主題稍後 的如何自訂中樞管線

如何啟用追蹤

若要啟用伺服器端追蹤,請將 system.diagnostics 元素新增至您的 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/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,但您從中樞類別呼叫的選項較少。 這是因為內容並未與用戶端的特定呼叫相關聯,因此任何需要瞭解目前連線識別碼的方法,例如 Clients.Others 、 或 Clients.CallerClients.OthersInGroup ,都無法使用。 可用選項如下:

  • 所有已連線的用戶端。

    context.Clients.All.addContosoChatMessageToPage(name, message);
    
  • 以連線識別碼識別的特定用戶端。

    context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);
    
  • 除了指定的用戶端之外,所有已連線的用戶端,都會以連線識別碼識別。

    context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • 指定群組中的所有已連線用戶端。

    context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • 指定群組中的所有已連線用戶端,但指定用戶端除外,以連線識別碼識別。

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    

如果您要從中樞類別中的方法呼叫非中樞類別,您可以傳入目前的連線識別碼,並將該識別碼與 、 或 搭配使用,以模擬 Clients.CallerClients.Others 、 或 Clients.OthersInGroupClients.GroupClients.ClientClients.AllExcept 在下列範例中,類別 MoveShapeHub 會將連接識別碼傳遞至 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 方法