在 ASP.NET Core 的 SignalR 中使用中樞
作者:Rachel Appel 和 Kevin Griffin
SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法,有助於即時通訊。 伺服器會定義用戶端呼叫的方法,而用戶端會定義伺服器呼叫的方法。 SignalR 也會啟用間接用戶端對用戶端通訊,一律由 SignalR 中樞調解,可在個別用戶端、群組或所有連線的用戶端之間傳送訊息。 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
方法會接收要呼叫的用戶端方法名稱以及任何參數。
Client
和 Caller
方法所傳回的物件也會包含 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 (或 AddKeyedScoped
或 AddKeyedTransient
) 進行註冊,以與索引鍵建立關聯。 藉由指定具有 [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 提供 OnConnectedAsync 和 OnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 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
/catch
與 async
/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 Appel 和 Kevin Griffin
SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法,有助於即時通訊。 伺服器會定義用戶端呼叫的方法,而用戶端會定義伺服器呼叫的方法。 SignalR 也會啟用間接用戶端對用戶端通訊,一律由 SignalR 中樞調解,可在個別用戶端、群組或所有連線的用戶端之間傳送訊息。 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
方法會接收要呼叫的用戶端方法名稱以及任何參數。
Client
和 Caller
方法所傳回的物件也會包含 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 提供 OnConnectedAsync 和 OnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 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
/catch
與 async
/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 Appel 和 Kevin Griffin
SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法,有助於即時通訊。 伺服器會定義用戶端呼叫的方法,而用戶端會定義伺服器呼叫的方法。 SignalR 也會啟用間接用戶端對用戶端通訊,一律由 SignalR 中樞調解,可在個別用戶端、群組或所有連線的用戶端之間傳送訊息。 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 提供 OnConnectedAsync 和 OnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 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
/catch
與 async
/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 Appel 和 Kevin Griffin
檢視或下載範例程式碼 \(英文\) (如何下載)
什麼是 SignalR 中樞
SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法,有助於即時通訊。 伺服器會定義用戶端呼叫的方法,而用戶端會定義伺服器呼叫的方法。 SignalR 也會啟用間接用戶端對用戶端通訊,一律由 SignalR 中樞調解,可在個別用戶端、群組或所有連線的用戶端之間傳送訊息。 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 提供 OnConnectedAsync 和 OnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 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 Appel 和 Kevin Griffin
檢視或下載範例程式碼 \(英文\) (如何下載)
什麼是 SignalR 中樞
SignalR 中樞 API 可讓已連線的用戶端在伺服器上呼叫方法,有助於即時通訊。 伺服器會定義用戶端呼叫的方法,而用戶端會定義伺服器呼叫的方法。 SignalR 也會啟用間接用戶端對用戶端通訊,一律由 SignalR 中樞調解,可在個別用戶端、群組或所有連線的用戶端之間傳送訊息。 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 提供 OnConnectedAsync 和 OnDisconnectedAsync 虛擬方法來管理和追蹤連線。 覆寫 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
屬性傳送至用戶端。 用戶端無法使用例外狀況上的堆疊追蹤和其他屬性。