在 ASP.NET Core 的 SignalR 中使用中樞

作者:Rachel AppelKevin Griffin

SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法。 伺服器會定義從用戶端呼叫的方法,而用戶端會定義從伺服器呼叫的方法。 SignalR 負責處理進行即時用戶端對伺服器和伺服器對用戶端通訊所需的所有項目。

設定 SignalR 中樞

若要註冊 SignalR 中樞所需的服務,請在 Program.cs 中呼叫 AddSignalR

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

若要設定 SignalR 端點,請也在 Program.cs 中呼叫 MapHub

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

注意

ASP.NET Core SignalR 伺服器端組件現在會隨 .NET Core SDK 一起安裝。 如需詳細資訊,請參閱共用架構中的 SignalR 組件

建立和使用中樞

宣告繼承自 Hub 的類別,以建立中樞。 將 public 方法新增至類別,使其可從用戶端進行呼叫:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

注意

中樞是暫時性的

  • 請不要將狀態儲存至中樞類別的屬性。 每個中樞方法呼叫都會在新中樞執行個體上執行。
  • 請不要透過相依性插入來直接具現化中樞。 若要從應用程式中的其他位置將訊息傳送至用戶端,請使用 IHubContext
  • 呼叫依存於保持運作中樞的非同步方法時,請使用 await。 例如,如果在沒有 await 的情況下呼叫 Clients.All.SendAsync(...) 這類方法,而且中樞方法在 SendAsync 完成之前完成,則此方法可能會失敗。

內容物件

Hub 類別包括 Context 屬性,而此屬性包含下列具有連線資訊的屬性:

屬性 說明
ConnectionId 取得 SignalR 所指派連線的唯一識別碼。 每個連線都有一個連線識別碼。
UserIdentifier 取得使用者識別碼。 SignalR 預設會使用與連線相關聯之 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為使用者識別碼。
User 取得與目前使用者相關聯的 ClaimsPrincipal
Items 取得索引鍵/值集合,而此集合可以用來在此連線的範圍內共用資料。 資料可以儲存至此集合,而且將會跨不同的中樞方法叫用以針對連線持續保存。
Features 取得連線上可用的功能集合。 目前,在大部分的情況下,不需要此集合,因此尚未詳細記載。
ConnectionAborted 取得 CancellationToken,其會在中止連線時通知。

Hub.Context 也包含下列方法:

方法 描述
GetHttpContext 針對連線,傳回 HttpContext,或者,如果連線未與 HTTP 要求相關聯,則傳回 null。 針對 HTTP 連線,請使用此方法來取得 HTTP 標頭和查詢字串這類資訊。
Abort 中止連線。

Clients 物件

Hub 類別包括 Clients 屬性,而此屬性包含伺服器與用戶端之間通訊的下列屬性:

屬性 說明
All 在所有已連線的用戶端上呼叫方法
Caller 在已叫用中樞方法的用戶端上呼叫方法
Others 在所有已連線的用戶端上呼叫方法,但已叫用方法的用戶端除外

Hub.Clients 也包含下列方法:

方法 描述
AllExcept 在所有已連線的用戶端上呼叫方法,但指定的連線除外
Client 在特定已連線的用戶端上呼叫方法
Clients 在特定已連線的用戶端上呼叫方法
Group 在所指定群組的所有連線上呼叫方法
GroupExcept 在所指定群組的所有連線上呼叫方法,但指定的連線除外
Groups 在多個連線群組上呼叫方法
OthersInGroup 在連線群組上呼叫方法,但不包括已叫用中樞方法的用戶端
User 在與特定使用者相關聯的所有連線上呼叫方法
Users 在與所指定使用者相關聯的所有連線上呼叫方法

上述各表中的每個屬性或方法都會使用 SendAsync 方法來傳回物件。 SendAsync 方法會接收要呼叫的用戶端方法名稱以及任何參數。

ClientCaller 方法所傳回的物件也會包含 InvokeAsync 方法,而此方法可以用來等候用戶端結果

將訊息傳送至用戶端

若要呼叫特定用戶端,請使用 Clients 物件的屬性。 在下列範例中,有三種中樞方法:

  • SendMessage 使用 Clients.All,以將訊息傳送至所有已連線的用戶端。
  • SendMessageToCaller 使用 Clients.Caller,以將訊息傳回給呼叫者。
  • SendMessageToGroup 會將訊息傳送至 SignalR Users 群組中的所有用戶端。
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

強型別中樞

使用 SendAsync 的缺點是其依賴字串來指定要呼叫的用戶端方法。 如果來自用戶端的方法名稱拼錯或遺漏,則這會向執行階段錯誤開放程式碼。

使用 SendAsync 的替代方式是使用 Hub<T> 來強型輸入 Hub 類別。 在下列範例中,ChatHub 用戶端方法已擷取至稱為 IChatClient 的介面:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

此介面可以用來將上述 ChatHub 範例重構為強型別:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

使用 Hub<IChatClient> 可啟用用戶端方法的編譯時間檢查。 這可防止使用字串所造成的問題,因為 Hub<T> 只能提供介面中所定義方法的存取權。 使用強型別 Hub<T> 會停用使用 SendAsync 的能力。

注意

Async 尾碼不會從方法名稱中移除。 除非使用 .on('MyMethodAsync') 來定義用戶端方法,否則請不要使用 MyMethodAsync 作為名稱。

用戶端結果

除了呼叫用戶端之外,伺服器還可以向用戶端要求結果。 這需要伺服器使用 ISingleClientProxy.InvokeAsync,而且需要用戶端透過其 .On 處理常式來傳回結果。

有兩種方式可以在伺服器上使用 API,而第一種方式是在 Hub 方法的 Clients 屬性上呼叫 Client(...)Caller

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

第二種方式是在 IHubContext<T> 執行個體上呼叫 Client(...)

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

強型別中樞也可以從介面方法中傳回值:

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

用戶端會傳回其 .On(...) 處理常式中的結果,如下所示:

.NET 用戶端

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Typescript 用戶端

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Java 用戶端

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

變更中樞方法的名稱

伺服器中樞方法名稱預設為 .NET 方法的名稱。 若要變更特定方法的這個預設行為,請使用 HubMethodName 屬性。 叫用方法時,用戶端應該使用此名稱,而不是 .NET 方法名稱:

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

將服務插入中樞

中樞建構函式可以接受來自 DI 的服務作為參數,而這些參數可以儲存至類別上的屬性,以用於中樞方法。

針對不同的中樞方法插入多個服務或作為撰寫程式碼的替代方式時,中樞方法也可以接受來自 DI 的服務。 預設會檢查中樞方法參數,並盡可能從 DI 進行解析。

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

如果不需要從服務對參數進行隱含解析,則請使用 DisableImplicitFromServicesParameters 將其停用。 若要在中樞方法中明確指定從 DI 解析的參數,請使用 DisableImplicitFromServicesParameters 選項,並使用 [FromServices] 屬性或自訂屬性,以在應該從 DI 解析的中樞方法參數上實作 IFromServiceMetadata

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

注意

此功能會利用 IServiceProviderIsService,其是由 DI 實作選擇性地實作。 如果應用程式的 DI 容器不支援此功能,則不支援將服務插入中樞方法。

相依性插入中的索引鍵服務支援

「索引鍵服務」是指使用索引鍵註冊和擷取相依性插入服務的機制。 服務會藉由呼叫 AddKeyedSingleton (或 AddKeyedScopedAddKeyedTransient) 進行註冊,以與索引鍵建立關聯。 藉由指定具有 [FromKeyedServices] 屬性的索引鍵來存取已註冊的服務。 下列程式碼示範如何使用索引鍵服務:

using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

app.MapRazorPages();
app.MapHub<MyHub>("/myHub");

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

public class MyHub : Hub
{
    public void SmallCacheMethod([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }

    public void BigCacheMethod([FromKeyedServices("big")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

處理連線的事件

SignalR 中樞 API 提供 OnConnectedAsyncOnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 OnConnectedAsync 虛擬方法,以在用戶端連線至中樞時執行動作,例如將其新增至群組:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

覆寫 OnDisconnectedAsync 虛擬方法,以在用戶端中斷連線時執行動作。 如果用戶端刻意中斷連線 (例如呼叫 connection.stop()),則會將 exception 參數設定為 null。 不過,如果用戶端因錯誤而中斷連線 (例如網路失敗),則 exception 參數會包含可描述失敗的例外狀況:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync 不需要在 中 OnDisconnectedAsync呼叫 ,系統會自動為您處理。

處理錯誤

中樞方法中所擲回的例外狀況會傳送至已叫用方法的用戶端。 在 JavaScript 用戶端上,invoke 方法會傳回 JavaScript Promise。 用戶端可以將 catch 處理常式附加至所傳回的 Promise,或搭配使用 try/catchasync/await 來處理例外狀況:

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

中樞擲回例外狀況時,不會關閉連線。 SignalR 預設會將一般錯誤訊息傳回給用戶端,如下列範例所示:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

非預期的例外狀況通常包含敏感性資訊,例如,資料庫連線失敗時所觸發例外狀況中的資料庫伺服器名稱。 SignalR 預設不會公開這些詳細的錯誤訊息作為安全性措施。 如需為何隱藏例外狀況詳細資料的詳細資訊,請參閱 ASP.NET Core SignalR 中的安全性考量

如果例外狀況必須傳播至用戶端,則請使用 HubException 類別。 如果在中樞方法中擲回 HubException,則 SignalR「會將整個例外狀況訊息傳送至用戶端」,而且未進行修改:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

注意

SignalR 只會將例外狀況的 Message 屬性傳送至用戶端。 用戶端無法使用例外狀況上的堆疊追蹤和其他屬性。

其他資源

作者:Rachel AppelKevin Griffin

SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法。 伺服器會定義從用戶端呼叫的方法,而用戶端會定義從伺服器呼叫的方法。 SignalR 負責處理進行即時用戶端對伺服器和伺服器對用戶端通訊所需的所有項目。

設定 SignalR 中樞

若要註冊 SignalR 中樞所需的服務,請在 Program.cs 中呼叫 AddSignalR

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

若要設定 SignalR 端點,請也在 Program.cs 中呼叫 MapHub

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

注意

ASP.NET Core SignalR 伺服器端組件現在會隨 .NET Core SDK 一起安裝。 如需詳細資訊,請參閱共用架構中的 SignalR 組件

建立和使用中樞

宣告繼承自 Hub 的類別,以建立中樞。 將 public 方法新增至類別,使其可從用戶端進行呼叫:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

注意

中樞是暫時性的

  • 請不要將狀態儲存至中樞類別的屬性。 每個中樞方法呼叫都會在新中樞執行個體上執行。
  • 請不要透過相依性插入來直接具現化中樞。 若要從應用程式中的其他位置將訊息傳送至用戶端,請使用 IHubContext
  • 呼叫依存於保持運作中樞的非同步方法時,請使用 await。 例如,如果在沒有 await 的情況下呼叫 Clients.All.SendAsync(...) 這類方法,而且中樞方法在 SendAsync 完成之前完成,則此方法可能會失敗。

內容物件

Hub 類別包括 Context 屬性,而此屬性包含下列具有連線資訊的屬性:

屬性 說明
ConnectionId 取得 SignalR 所指派連線的唯一識別碼。 每個連線都有一個連線識別碼。
UserIdentifier 取得使用者識別碼。 SignalR 預設會使用與連線相關聯之 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為使用者識別碼。
User 取得與目前使用者相關聯的 ClaimsPrincipal
Items 取得索引鍵/值集合,而此集合可以用來在此連線的範圍內共用資料。 資料可以儲存至此集合,而且將會跨不同的中樞方法叫用以針對連線持續保存。
Features 取得連線上可用的功能集合。 目前,在大部分的情況下,不需要此集合,因此尚未詳細記載。
ConnectionAborted 取得 CancellationToken,其會在中止連線時通知。

Hub.Context 也包含下列方法:

方法 描述
GetHttpContext 針對連線,傳回 HttpContext,或者,如果連線未與 HTTP 要求相關聯,則傳回 null。 針對 HTTP 連線,請使用此方法來取得 HTTP 標頭和查詢字串這類資訊。
Abort 中止連線。

Clients 物件

Hub 類別包括 Clients 屬性,而此屬性包含伺服器與用戶端之間通訊的下列屬性:

屬性 說明
All 在所有已連線的用戶端上呼叫方法
Caller 在已叫用中樞方法的用戶端上呼叫方法
Others 在所有已連線的用戶端上呼叫方法,但已叫用方法的用戶端除外

Hub.Clients 也包含下列方法:

方法 描述
AllExcept 在所有已連線的用戶端上呼叫方法,但指定的連線除外
Client 在特定已連線的用戶端上呼叫方法
Clients 在特定已連線的用戶端上呼叫方法
Group 在所指定群組的所有連線上呼叫方法
GroupExcept 在所指定群組的所有連線上呼叫方法,但指定的連線除外
Groups 在多個連線群組上呼叫方法
OthersInGroup 在連線群組上呼叫方法,但不包括已叫用中樞方法的用戶端
User 在與特定使用者相關聯的所有連線上呼叫方法
Users 在與所指定使用者相關聯的所有連線上呼叫方法

上述各表中的每個屬性或方法都會使用 SendAsync 方法來傳回物件。 SendAsync 方法會接收要呼叫的用戶端方法名稱以及任何參數。

ClientCaller 方法所傳回的物件也會包含 InvokeAsync 方法,而此方法可以用來等候用戶端結果

將訊息傳送至用戶端

若要呼叫特定用戶端,請使用 Clients 物件的屬性。 在下列範例中,有三種中樞方法:

  • SendMessage 使用 Clients.All,以將訊息傳送至所有已連線的用戶端。
  • SendMessageToCaller 使用 Clients.Caller,以將訊息傳回給呼叫者。
  • SendMessageToGroup 會將訊息傳送至 SignalR Users 群組中的所有用戶端。
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

強型別中樞

使用 SendAsync 的缺點是其依賴字串來指定要呼叫的用戶端方法。 如果來自用戶端的方法名稱拼錯或遺漏,則這會向執行階段錯誤開放程式碼。

使用 SendAsync 的替代方式是使用 Hub<T> 來強型輸入 Hub 類別。 在下列範例中,ChatHub 用戶端方法已擷取至稱為 IChatClient 的介面:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

此介面可以用來將上述 ChatHub 範例重構為強型別:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

使用 Hub<IChatClient> 可啟用用戶端方法的編譯時間檢查。 這可防止使用字串所造成的問題,因為 Hub<T> 只能提供介面中所定義方法的存取權。 使用強型別 Hub<T> 會停用使用 SendAsync 的能力。

注意

Async 尾碼不會從方法名稱中移除。 除非使用 .on('MyMethodAsync') 來定義用戶端方法,否則請不要使用 MyMethodAsync 作為名稱。

用戶端結果

除了呼叫用戶端之外,伺服器還可以向用戶端要求結果。 這需要伺服器使用 ISingleClientProxy.InvokeAsync,而且需要用戶端透過其 .On 處理常式來傳回結果。

有兩種方式可以在伺服器上使用 API,而第一種方式是在 Hub 方法的 Clients 屬性上呼叫 Client(...)Caller

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

第二種方式是在 IHubContext<T> 執行個體上呼叫 Client(...)

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

強型別中樞也可以從介面方法中傳回值:

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

用戶端會傳回其 .On(...) 處理常式中的結果,如下所示:

.NET 用戶端

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Typescript 用戶端

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Java 用戶端

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

變更中樞方法的名稱

伺服器中樞方法名稱預設為 .NET 方法的名稱。 若要變更特定方法的這個預設行為,請使用 HubMethodName 屬性。 叫用方法時,用戶端應該使用此名稱,而不是 .NET 方法名稱:

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

將服務插入中樞

中樞建構函式可以接受來自 DI 的服務作為參數,而這些參數可以儲存至類別上的屬性,以用於中樞方法。

針對不同的中樞方法插入多個服務或作為撰寫程式碼的替代方式時,中樞方法也可以接受來自 DI 的服務。 預設會檢查中樞方法參數,並盡可能從 DI 進行解析。

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

如果不需要從服務對參數進行隱含解析,則請使用 DisableImplicitFromServicesParameters 將其停用。 若要在中樞方法中明確指定從 DI 解析的參數,請使用 DisableImplicitFromServicesParameters 選項,並使用 [FromServices] 屬性或自訂屬性,以在應該從 DI 解析的中樞方法參數上實作 IFromServiceMetadata

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

注意

此功能會利用 IServiceProviderIsService,其是由 DI 實作選擇性地實作。 如果應用程式的 DI 容器不支援此功能,則不支援將服務插入中樞方法。

處理連線的事件

SignalR 中樞 API 提供 OnConnectedAsyncOnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 OnConnectedAsync 虛擬方法,以在用戶端連線至中樞時執行動作,例如將其新增至群組:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

覆寫 OnDisconnectedAsync 虛擬方法,以在用戶端中斷連線時執行動作。 如果用戶端刻意中斷連線 (例如呼叫 connection.stop()),則會將 exception 參數設定為 null。 不過,如果用戶端因錯誤而中斷連線 (例如網路失敗),則 exception 參數會包含可描述失敗的例外狀況:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

不需要在 OnDisconnectedAsync 中呼叫 RemoveFromGroupAsync,系統會自動為您處理。

處理錯誤

中樞方法中所擲回的例外狀況會傳送至已叫用方法的用戶端。 在 JavaScript 用戶端上,invoke 方法會傳回 JavaScript Promise。 用戶端可以將 catch 處理常式附加至所傳回的 Promise,或搭配使用 try/catchasync/await 來處理例外狀況:

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

中樞擲回例外狀況時,不會關閉連線。 SignalR 預設會將一般錯誤訊息傳回給用戶端,如下列範例所示:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

非預期的例外狀況通常包含敏感性資訊,例如,資料庫連線失敗時所觸發例外狀況中的資料庫伺服器名稱。 SignalR 預設不會公開這些詳細的錯誤訊息作為安全性措施。 如需為何隱藏例外狀況詳細資料的詳細資訊,請參閱 ASP.NET Core SignalR 中的安全性考量

如果例外狀況必須傳播至用戶端,則請使用 HubException 類別。 如果在中樞方法中擲回 HubException,則 SignalR「會將整個例外狀況訊息傳送至用戶端」,而且未進行修改:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

注意

SignalR 只會將例外狀況的 Message 屬性傳送至用戶端。 用戶端無法使用例外狀況上的堆疊追蹤和其他屬性。

其他資源

作者:Rachel AppelKevin Griffin

SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法。 伺服器會定義從用戶端呼叫的方法,而用戶端會定義從伺服器呼叫的方法。 SignalR 負責處理進行即時用戶端對伺服器和伺服器對用戶端通訊所需的所有項目。

設定 SignalR 中樞

若要註冊 SignalR 中樞所需的服務,請在 Program.cs 中呼叫 AddSignalR

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

若要設定 SignalR 端點,請也在 Program.cs 中呼叫 MapHub

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

注意

ASP.NET Core SignalR 伺服器端組件現在會隨 .NET Core SDK 一起安裝。 如需詳細資訊,請參閱共用架構中的 SignalR 組件

建立和使用中樞

宣告繼承自 Hub 的類別,以建立中樞。 將 public 方法新增至類別,使其可從用戶端進行呼叫:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

注意

中樞是暫時性的

  • 請不要將狀態儲存至中樞類別的屬性。 每個中樞方法呼叫都會在新中樞執行個體上執行。
  • 請不要透過相依性插入來直接具現化中樞。 若要從應用程式中的其他位置將訊息傳送至用戶端,請使用 IHubContext
  • 呼叫依存於保持運作中樞的非同步方法時,請使用 await。 例如,如果在沒有 await 的情況下呼叫 Clients.All.SendAsync(...) 這類方法,而且中樞方法在 SendAsync 完成之前完成,則此方法可能會失敗。

內容物件

Hub 類別包括 Context 屬性,而此屬性包含下列具有連線資訊的屬性:

屬性 說明
ConnectionId 取得 SignalR 所指派連線的唯一識別碼。 每個連線都有一個連線識別碼。
UserIdentifier 取得使用者識別碼。 SignalR 預設會使用與連線相關聯之 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為使用者識別碼。
User 取得與目前使用者相關聯的 ClaimsPrincipal
Items 取得索引鍵/值集合,而此集合可以用來在此連線的範圍內共用資料。 資料可以儲存至此集合,而且將會跨不同的中樞方法叫用以針對連線持續保存。
Features 取得連線上可用的功能集合。 目前,在大部分的情況下,不需要此集合,因此尚未詳細記載。
ConnectionAborted 取得 CancellationToken,其會在中止連線時通知。

Hub.Context 也包含下列方法:

方法 描述
GetHttpContext 針對連線,傳回 HttpContext,或者,如果連線未與 HTTP 要求相關聯,則傳回 null。 針對 HTTP 連線,請使用此方法來取得 HTTP 標頭和查詢字串這類資訊。
Abort 中止連線。

Clients 物件

Hub 類別包括 Clients 屬性,而此屬性包含伺服器與用戶端之間通訊的下列屬性:

屬性 說明
All 在所有已連線的用戶端上呼叫方法
Caller 在已叫用中樞方法的用戶端上呼叫方法
Others 在所有已連線的用戶端上呼叫方法,但已叫用方法的用戶端除外

Hub.Clients 也包含下列方法:

方法 描述
AllExcept 在所有已連線的用戶端上呼叫方法,但指定的連線除外
Client 在特定已連線的用戶端上呼叫方法
Clients 在特定已連線的用戶端上呼叫方法
Group 在所指定群組的所有連線上呼叫方法
GroupExcept 在所指定群組的所有連線上呼叫方法,但指定的連線除外
Groups 在多個連線群組上呼叫方法
OthersInGroup 在連線群組上呼叫方法,但不包括已叫用中樞方法的用戶端
User 在與特定使用者相關聯的所有連線上呼叫方法
Users 在與所指定使用者相關聯的所有連線上呼叫方法

上述各表中的每個屬性或方法都會使用 SendAsync 方法來傳回物件。 SendAsync 方法會接收要呼叫的用戶端方法名稱以及任何參數。

將訊息傳送至用戶端

若要呼叫特定用戶端,請使用 Clients 物件的屬性。 在下列範例中,有三種中樞方法:

  • SendMessage 使用 Clients.All,以將訊息傳送至所有已連線的用戶端。
  • SendMessageToCaller 使用 Clients.Caller,以將訊息傳回給呼叫者。
  • SendMessageToGroup 會將訊息傳送至 SignalR Users 群組中的所有用戶端。
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

強型別中樞

使用 SendAsync 的缺點是其依賴字串來指定要呼叫的用戶端方法。 如果來自用戶端的方法名稱拼錯或遺漏,則這會向執行階段錯誤開放程式碼。

使用 SendAsync 的替代方式是使用 Hub<T> 來強型輸入 Hub 類別。 在下列範例中,ChatHub 用戶端方法已擷取至稱為 IChatClient 的介面:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

此介面可以用來將上述 ChatHub 範例重構為強型別:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

使用 Hub<IChatClient> 可啟用用戶端方法的編譯時間檢查。 這可防止使用字串所造成的問題,因為 Hub<T> 只能提供介面中所定義方法的存取權。 使用強型別 Hub<T> 會停用使用 SendAsync 的能力。

注意

Async 尾碼不會從方法名稱中移除。 除非使用 .on('MyMethodAsync') 來定義用戶端方法,否則請不要使用 MyMethodAsync 作為名稱。

變更中樞方法的名稱

伺服器中樞方法名稱預設為 .NET 方法的名稱。 若要變更特定方法的這個預設行為,請使用 HubMethodName 屬性。 叫用方法時,用戶端應該使用此名稱,而不是 .NET 方法名稱:

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

處理連線的事件

SignalR 中樞 API 提供 OnConnectedAsyncOnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 OnConnectedAsync 虛擬方法,以在用戶端連線至中樞時執行動作,例如將其新增至群組:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

覆寫 OnDisconnectedAsync 虛擬方法,以在用戶端中斷連線時執行動作。 如果用戶端刻意中斷連線 (例如呼叫 connection.stop()),則會將 exception 參數設定為 null。 不過,如果用戶端因錯誤而中斷連線 (例如網路失敗),則 exception 參數會包含可描述失敗的例外狀況:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

不需要在 OnDisconnectedAsync 中呼叫 RemoveFromGroupAsync,系統會自動為您處理。

處理錯誤

中樞方法中所擲回的例外狀況會傳送至已叫用方法的用戶端。 在 JavaScript 用戶端上,invoke 方法會傳回 JavaScript Promise。 用戶端可以將 catch 處理常式附加至所傳回的 Promise,或搭配使用 try/catchasync/await 來處理例外狀況:

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

中樞擲回例外狀況時,不會關閉連線。 SignalR 預設會將一般錯誤訊息傳回給用戶端,如下列範例所示:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

非預期的例外狀況通常包含敏感性資訊,例如,資料庫連線失敗時所觸發例外狀況中的資料庫伺服器名稱。 SignalR 預設不會公開這些詳細的錯誤訊息作為安全性措施。 如需為何隱藏例外狀況詳細資料的詳細資訊,請參閱 ASP.NET Core SignalR 中的安全性考量

如果例外狀況必須傳播至用戶端,則請使用 HubException 類別。 如果在中樞方法中擲回 HubException,則 SignalR「會將整個例外狀況訊息傳送至用戶端」,而且未進行修改:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

注意

SignalR 只會將例外狀況的 Message 屬性傳送至用戶端。 用戶端無法使用例外狀況上的堆疊追蹤和其他屬性。

其他資源

作者:Rachel AppelKevin Griffin

檢視或下載範例程式碼(如何下載)

什麼是 SignalR 中樞

SignalR 中樞 API 可讓您從伺服器於已連線的用戶端上呼叫方法。 在伺服器程式碼中,您可以定義用戶端所呼叫的方法。 在用戶端程式碼中,您可以定義從伺服器呼叫的方法。 SignalR 負責幕後的所有項目,讓即時用戶端對伺服器和伺服器對用戶端通訊成為可能。

設定 SignalR 中樞

SignalR 中介軟體需要一些服務,而這些服務是呼叫 AddSignalR 所設定:

services.AddSignalR();

將 SignalR 功能新增至 ASP.NET Core 應用程式時,請在 Startup.Configure 方法的 UseEndpoints 回呼中呼叫 MapHub,以設定 SignalR 路由:

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/chathub");
});

注意

ASP.NET Core SignalR 伺服器端組件現在會隨 .NET Core SDK 一起安裝。 如需詳細資訊,請參閱共用架構中的 SignalR 組件

建立和使用中樞

宣告繼承自 Hub 的類別來建立中樞,並在其中新增公用方法。 用戶端可以呼叫定義為 public 的方法:

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

您可以指定傳回類型和參數 (包括複雜類型和陣列),就像在任何 C# 方法中一樣。 SignalR 會處理參數和傳回值中複雜物件和陣列的序列化和還原序列化。

注意

中樞是暫時性的:

  • 請不要將狀態儲存至中樞類別上的屬性。 每個中樞方法呼叫都會在新中樞執行個體上執行。
  • 請不要透過相依性插入來直接具現化中樞。 若要從應用程式中的其他位置將訊息傳送至用戶端,請使用 IHubContext
  • 呼叫依存於保持運作中樞的非同步方法時,請使用 await。 例如,如果在沒有 await 的情況下呼叫 Clients.All.SendAsync(...) 這類方法,而且中樞方法在 SendAsync 完成之前完成,則此方法可能會失敗。

內容物件

Hub 類別具有 Context 屬性,而此屬性包含下列具有連線相關資訊的屬性:

屬性 說明
ConnectionId 取得 SignalR 所指派連線的唯一識別碼。 每個連線都有一個連線識別碼。
UserIdentifier 取得使用者識別碼。 SignalR 預設會使用與連線相關聯之 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為使用者識別碼。
User 取得與目前使用者相關聯的 ClaimsPrincipal
Items 取得索引鍵/值集合,而此集合可以用來在此連線的範圍內共用資料。 資料可以儲存至此集合,而且將會跨不同的中樞方法叫用以針對連線持續保存。
Features 取得連線上可用的功能集合。 目前,在大部分的情況下,不需要此集合,因此尚未詳細記載。
ConnectionAborted 取得 CancellationToken,其會在中止連線時通知。

Hub.Context 也包含下列方法:

方法 描述
GetHttpContext 針對連線,傳回 HttpContext,或者,如果連線未與 HTTP 要求相關聯,則傳回 null。 針對 HTTP 連線,您可以使用此方法來取得 HTTP 標頭和查詢字串這類資訊。
Abort 中止連線。

Clients 物件

Hub 類別具有 Clients 屬性,而此屬性包含伺服器與用戶端之間通訊的下列屬性:

屬性 說明
All 在所有已連線的用戶端上呼叫方法
Caller 在已叫用中樞方法的用戶端上呼叫方法
Others 在所有已連線的用戶端上呼叫方法,但已叫用方法的用戶端除外

Hub.Clients 也包含下列方法:

方法 描述
AllExcept 在所有已連線的用戶端上呼叫方法,但指定的連線除外
Client 在特定已連線的用戶端上呼叫方法
Clients 在特定已連線的用戶端上呼叫方法
Group 在所指定群組的所有連線上呼叫方法
GroupExcept 在所指定群組的所有連線上呼叫方法,但指定的連線除外
Groups 在多個連線群組上呼叫方法
OthersInGroup 在連線群組上呼叫方法,但不包括已叫用中樞方法的用戶端
User 在與特定使用者相關聯的所有連線上呼叫方法
Users 在與所指定使用者相關聯的所有連線上呼叫方法

上述各表中的每個屬性或方法都會使用 SendAsync 方法來傳回物件。 SendAsync 方法可讓您提供要呼叫之用戶端方法的名稱和參數。

將訊息傳送至用戶端

若要呼叫特定用戶端,請使用 Clients 物件的屬性。 在下列範例中,有三個中樞方法:

  • SendMessage 使用 Clients.All,以將訊息傳送至所有已連線的用戶端。
  • SendMessageToCaller 使用 Clients.Caller,以將訊息傳回給呼叫者。
  • SendMessageToGroup 會將訊息傳送至 SignalR Users 群組中的所有用戶端。
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

強型別中樞

使用 SendAsync 的缺點是其依賴 magic 字串來指定要呼叫的用戶端方法。 如果來自用戶端的方法名稱拼錯或遺漏,則這會向執行階段錯誤開放程式碼。

使用 SendAsync 的替代方式是使用 Hub<T> 來強型輸入 Hub。 在下列範例中,ChatHub 用戶端方法已擷取至稱為 IChatClient 的介面。

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

此介面可以用來重構上述 ChatHub 範例:

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

使用 Hub<IChatClient> 可啟用用戶端方法的編譯時間檢查。 這可防止使用 magic 字串所造成的問題,因為 Hub<T> 只能提供介面中所定義方法的存取權。

使用強型別 Hub<T> 會停用使用 SendAsync 的能力。 介面上所定義的任何方法仍然可以定義為非同步。 事實上,所有這些方法都應該傳回 Task。 因為其為介面,所以請不要使用 async 關鍵字。 例如:

public interface IClient
{
    Task ClientMethod();
}

注意

Async 尾碼不會從方法名稱中移除。 除非您的用戶端方法是使用 .on('MyMethodAsync') 所定義,否則您不應該使用 MyMethodAsync 作為名稱。

變更中樞方法的名稱

伺服器中樞方法名稱預設為 .NET 方法的名稱。 不過,您可以使用 HubMethodName 屬性來變更此預設值,以及手動指定方法的名稱。 叫用方法時,用戶端應該使用此名稱,而不是 .NET 方法名稱:

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

處理連線的事件

SignalR 中樞 API 提供 OnConnectedAsyncOnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 OnConnectedAsync 虛擬方法,以在用戶端連線至中樞時執行動作,例如將其新增至群組:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

覆寫 OnDisconnectedAsync 虛擬方法,以在用戶端中斷連線時執行動作。 如果用戶端刻意中斷連線 (例如呼叫 connection.stop()),則 exception 參數將會是 null。 不過,如果用戶端因錯誤而中斷連線 (例如網路失敗),則 exception 參數將會包含可描述失敗的例外狀況:

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

不需要在 OnDisconnectedAsync 中呼叫 RemoveFromGroupAsync,系統會自動為您處理。

警告

安全性警告:如果 SignalR 伺服器或用戶端版本是 ASP.NET Core 2.2 或更早版本,則公開 ConnectionId 可能會導致惡意模擬。

處理錯誤

您中樞方法中所擲回的例外狀況會傳送至已叫用方法的用戶端。 在 JavaScript 用戶端上,invoke 方法會傳回 JavaScript Promise。 用戶端收到使用 catch 將處理常式附加至 Promise 的錯誤時,會對其進行叫用,並將其傳遞為 JavaScript Error 物件:

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

如果您的中樞擲回例外狀況,則不會關閉連線。 SignalR 預設會將一般錯誤訊息傳回給用戶端。 例如:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

非預期的例外狀況通常包含敏感性資訊,例如,資料庫連線失敗時所觸發例外狀況中的資料庫伺服器名稱。 SignalR 預設不會公開這些詳細的錯誤訊息作為安全性措施。 如需為何隱藏例外狀況詳細資料的詳細資訊,請參閱 ASP.NET Core SignalR 中的安全性考量

如果您有「確實」想要傳播至用戶端的例外狀況,則可以使用 HubException 類別。 如果您從中樞方法擲回 HubException,則 SignalR「將」整個訊息傳送至用戶端,而且未進行修改:

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

注意

SignalR 只會將例外狀況的 Message 屬性傳送至用戶端。 用戶端無法使用例外狀況上的堆疊追蹤和其他屬性。

其他資源

作者:Rachel AppelKevin Griffin

檢視或下載範例程式碼(如何下載)

什麼是 SignalR 中樞

SignalR 中樞 API 可讓您從伺服器於已連線的用戶端上呼叫方法。 在伺服器程式碼中,您可以定義用戶端所呼叫的方法。 在用戶端程式碼中,您可以定義從伺服器呼叫的方法。 SignalR 負責幕後的所有項目,讓即時用戶端對伺服器和伺服器對用戶端通訊成為可能。

設定 SignalR 中樞

SignalR 中介軟體需要一些服務,而這些服務是呼叫 AddSignalR 所設定:

services.AddSignalR();

將 SignalR 功能新增至 ASP.NET Core 應用程式時,請在 Startup.Configure 方法中呼叫 UseSignalR,以設定 SignalR 路由:

app.UseSignalR(route =>
{
    route.MapHub<ChatHub>("/chathub");
});

建立和使用中樞

宣告繼承自 Hub 的類別來建立中樞,並在其中新增公用方法。 用戶端可以呼叫定義為 public 的方法:

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

您可以指定傳回類型和參數 (包括複雜類型和陣列),就像在任何 C# 方法中一樣。 SignalR 會處理參數和傳回值中複雜物件和陣列的序列化和還原序列化。

注意

中樞是暫時性的:

  • 請不要將狀態儲存至中樞類別上的屬性。 每個中樞方法呼叫都會在新中樞執行個體上執行。
  • 請不要透過相依性插入來直接具現化中樞。 若要從應用程式中的其他位置將訊息傳送至用戶端,請使用 IHubContext
  • 呼叫依存於保持運作中樞的非同步方法時,請使用 await。 例如,如果在沒有 await 的情況下呼叫 Clients.All.SendAsync(...) 這類方法,而且中樞方法在 SendAsync 完成之前完成,則此方法可能會失敗。

內容物件

Hub 類別具有 Context 屬性,而此屬性包含下列具有連線相關資訊的屬性:

屬性 說明
ConnectionId 取得 SignalR 所指派連線的唯一識別碼。 每個連線都有一個連線識別碼。
UserIdentifier 取得使用者識別碼。 SignalR 預設會使用與連線相關聯之 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為使用者識別碼。
User 取得與目前使用者相關聯的 ClaimsPrincipal
Items 取得索引鍵/值集合,而此集合可以用來在此連線的範圍內共用資料。 資料可以儲存至此集合,而且將會跨不同的中樞方法叫用以針對連線持續保存。
Features 取得連線上可用的功能集合。 目前,在大部分的情況下,不需要此集合,因此尚未詳細記載。
ConnectionAborted 取得 CancellationToken,其會在中止連線時通知。

Hub.Context 也包含下列方法:

方法 描述
GetHttpContext 針對連線,傳回 HttpContext,或者,如果連線未與 HTTP 要求相關聯,則傳回 null。 針對 HTTP 連線,您可以使用此方法來取得 HTTP 標頭和查詢字串這類資訊。
Abort 中止連線。

Clients 物件

Hub 類別具有 Clients 屬性,而此屬性包含伺服器與用戶端之間通訊的下列屬性:

屬性 說明
All 在所有已連線的用戶端上呼叫方法
Caller 在已叫用中樞方法的用戶端上呼叫方法
Others 在所有已連線的用戶端上呼叫方法,但已叫用方法的用戶端除外

Hub.Clients 也包含下列方法:

方法 描述
AllExcept 在所有已連線的用戶端上呼叫方法,但指定的連線除外
Client 在特定已連線的用戶端上呼叫方法
Clients 在特定已連線的用戶端上呼叫方法
Group 在所指定群組的所有連線上呼叫方法
GroupExcept 在所指定群組的所有連線上呼叫方法,但指定的連線除外
Groups 在多個連線群組上呼叫方法
OthersInGroup 在連線群組上呼叫方法,但不包括已叫用中樞方法的用戶端
User 在與特定使用者相關聯的所有連線上呼叫方法
Users 在與所指定使用者相關聯的所有連線上呼叫方法

上述各表中的每個屬性或方法都會使用 SendAsync 方法來傳回物件。 SendAsync 方法可讓您提供要呼叫之用戶端方法的名稱和參數。

將訊息傳送至用戶端

若要呼叫特定用戶端,請使用 Clients 物件的屬性。 在下列範例中,有三個中樞方法:

  • SendMessage 使用 Clients.All,以將訊息傳送至所有已連線的用戶端。
  • SendMessageToCaller 使用 Clients.Caller,以將訊息傳回給呼叫者。
  • SendMessageToGroup 會將訊息傳送至 SignalR Users 群組中的所有用戶端。
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

強型別中樞

使用 SendAsync 的缺點是其依賴 magic 字串來指定要呼叫的用戶端方法。 如果來自用戶端的方法名稱拼錯或遺漏,則這會向執行階段錯誤開放程式碼。

使用 SendAsync 的替代方式是使用 Hub<T> 來強型輸入 Hub。 在下列範例中,ChatHub 用戶端方法已擷取至稱為 IChatClient 的介面。

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

此介面可以用來重構上述 ChatHub 範例:

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

使用 Hub<IChatClient> 可啟用用戶端方法的編譯時間檢查。 這可防止使用 magic 字串所造成的問題,因為 Hub<T> 只能提供介面中所定義方法的存取權。

使用強型別 Hub<T> 會停用使用 SendAsync 的能力。 介面上所定義的任何方法仍然可以定義為非同步。 事實上,所有這些方法都應該傳回 Task。 因為其為介面,所以請不要使用 async 關鍵字。 例如:

public interface IClient
{
    Task ClientMethod();
}

注意

Async 尾碼不會從方法名稱中移除。 除非您的用戶端方法是使用 .on('MyMethodAsync') 所定義,否則您不應該使用 MyMethodAsync 作為名稱。

變更中樞方法的名稱

伺服器中樞方法名稱預設為 .NET 方法的名稱。 不過,您可以使用 HubMethodName 屬性來變更此預設值,以及手動指定方法的名稱。 叫用方法時,用戶端應該使用此名稱,而不是 .NET 方法名稱:

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

處理連線的事件

SignalR 中樞 API 提供 OnConnectedAsyncOnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 OnConnectedAsync 虛擬方法,以在用戶端連線至中樞時執行動作,例如將其新增至群組:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

覆寫 OnDisconnectedAsync 虛擬方法,以在用戶端中斷連線時執行動作。 如果用戶端刻意中斷連線 (例如呼叫 connection.stop()),則 exception 參數將會是 null。 不過,如果用戶端因錯誤而中斷連線 (例如網路失敗),則 exception 參數將會包含可描述失敗的例外狀況:

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

不需要在 OnDisconnectedAsync 中呼叫 RemoveFromGroupAsync,系統會自動為您處理。

警告

安全性警告:如果 SignalR 伺服器或用戶端版本是 ASP.NET Core 2.2 或更早版本,則公開 ConnectionId 可能會導致惡意模擬。

處理錯誤

您中樞方法中所擲回的例外狀況會傳送至已叫用方法的用戶端。 在 JavaScript 用戶端上,invoke 方法會傳回 JavaScript Promise。 用戶端收到使用 catch 將處理常式附加至 Promise 的錯誤時,會對其進行叫用,並將其傳遞為 JavaScript Error 物件:

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

如果您的中樞擲回例外狀況,則不會關閉連線。 SignalR 預設會將一般錯誤訊息傳回給用戶端。 例如:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

非預期的例外狀況通常包含敏感性資訊,例如,資料庫連線失敗時所觸發例外狀況中的資料庫伺服器名稱。 SignalR 預設不會公開這些詳細的錯誤訊息作為安全性措施。 如需為何隱藏例外狀況詳細資料的詳細資訊,請參閱 ASP.NET Core SignalR 中的安全性考量

如果您有「確實」想要傳播至用戶端的例外狀況,則可以使用 HubException 類別。 如果您從中樞方法擲回 HubException,則 SignalR「將」整個訊息傳送至用戶端,而且未進行修改:

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

注意

SignalR 只會將例外狀況的 Message 屬性傳送至用戶端。 用戶端無法使用例外狀況上的堆疊追蹤和其他屬性。

其他資源