使用适用于 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 | 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。 |
UserIdentifier | 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。 |
User | 获取与当前用户关联的 ClaimsPrincipal。 |
Items | 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。 |
Features | 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。 |
ConnectionAborted | 获取一个 CancellationToken,它会在连接中止时发出通知。 |
Hub.Context 还包含以下方法:
方法 | 说明 |
---|---|
GetHttpContext | 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null 。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。 |
Abort | 中止连接。 |
客户端对象
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,第一种方法是在中心方法中对 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 容器不支持此功能,则不支持将服务注入中心方法。
依赖项注入中的键控服务支持
密钥服务是指使用密钥注册和检索依赖项注入 (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
处理程序附加到返回的承诺,或使用和 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 | 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。 |
UserIdentifier | 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。 |
User | 获取与当前用户关联的 ClaimsPrincipal。 |
Items | 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。 |
Features | 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。 |
ConnectionAborted | 获取一个 CancellationToken,它会在连接中止时发出通知。 |
Hub.Context 还包含以下方法:
方法 | 说明 |
---|---|
GetHttpContext | 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null 。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。 |
Abort | 中止连接。 |
客户端对象
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,第一种方法是在中心方法中对 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
处理程序附加到返回的承诺,或使用和 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 | 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。 |
UserIdentifier | 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。 |
User | 获取与当前用户关联的 ClaimsPrincipal。 |
Items | 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。 |
Features | 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。 |
ConnectionAborted | 获取一个 CancellationToken,它会在连接中止时发出通知。 |
Hub.Context 还包含以下方法:
方法 | 说明 |
---|---|
GetHttpContext | 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null 。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。 |
Abort | 中止连接。 |
客户端对象
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
处理程序附加到返回的承诺,或使用和 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();
向 ASP.NET Core 应用添加 SignalR 功能时,通过在 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 | 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。 |
UserIdentifier | 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。 |
User | 获取与当前用户关联的 ClaimsPrincipal。 |
Items | 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。 |
Features | 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。 |
ConnectionAborted | 获取一个 CancellationToken,它会在连接中止时发出通知。 |
Hub.Context 还包含以下方法:
方法 | 说明 |
---|---|
GetHttpContext | 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null 。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。 |
Abort | 中止连接。 |
客户端对象
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
的缺点在于,它依赖于魔幻字符串来指定要调用的客户端方法。 如果客户端中的方法名称拼写错误或缺失,则这会使代码可能出现运行时错误。
使用 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>
可以对客户端方法进行编译时检查。 这可防止由于使用魔幻字符串而导致的问题,因为 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
收到处理程序附加到承诺的错误时,它会作为 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();
向 ASP.NET Core 应用添加 SignalR 功能时,通过在 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 | 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。 |
UserIdentifier | 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作为用户标识符。 |
User | 获取与当前用户关联的 ClaimsPrincipal。 |
Items | 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。 |
Features | 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。 |
ConnectionAborted | 获取一个 CancellationToken,它会在连接中止时发出通知。 |
Hub.Context 还包含以下方法:
方法 | 说明 |
---|---|
GetHttpContext | 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null 。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。 |
Abort | 中止连接。 |
客户端对象
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
的缺点在于,它依赖于魔幻字符串来指定要调用的客户端方法。 如果客户端中的方法名称拼写错误或缺失,则这会使代码可能出现运行时错误。
使用 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>
可以对客户端方法进行编译时检查。 这可防止由于使用魔幻字符串而导致的问题,因为 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
收到处理程序附加到承诺的错误时,它会作为 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
属性发送到客户端。 异常中的堆栈跟踪和其他属性不可供客户端使用。