使用适用于 ASP.NET Core 的 SignalR 中的中心

作者:Rachel AppelKevin Griffin

中心 SignalR API 使连接的客户端能够在服务器上调用方法。 服务器定义从客户端调用的方法,客户端定义从服务器调用的方法。 SignalR 处理使实时客户端到服务器和服务器到客户端通信成为可能所需的所有内容。

配置 SignalR 中心

若要注册中心所需的SignalR服务,请调用AddSignalRProgram.cs

var builder = WebApplication.CreateBuilder(args);

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

若要配置 SignalR 终结点,请调用 MapHub,另请在 Program.cs

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

app.Run();

创建和使用中心

通过声明继承自 Hub的类来创建中心。 将方法添加到 public 类,使其可从客户端调用:

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

注意

中心是 暂时性的

  • 不要将状态存储在中心类的属性中。 每个中心方法调用在新的中心实例上执行。
  • 调用依赖于保持活动状态的中心的异步方法时请使用 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 连接,或者 null 如果连接未与 HTTP 请求关联。 对于 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 化类 Hub<T>。 In the following example, the ChatHub client method has been extracted out into an interface called 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 a0/>。 但是,如果客户端因错误(如网络故障)而断开连接,则 exception 参数包含描述失败的异常:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnDisconnectedAsync(exception);
}

处理错误

在中心方法中引发的异常将发送到调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript 承诺。 客户端可以将处理程序附加到catch返回的承诺,或者用于asyncawaittry/catch/处理异常:

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 CoreSignalR中的安全注意事项

如果必须将异常条件传播到客户端,请使用 HubException 该类。 如果在中心方法中引发 a, 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();

向 ASP.NET Core应用添加SignalR功能时,通过在方法的UseEndpoints回调中Startup.Configure调用MapHub设置SignalR路由:

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.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 会处理参数和返回值中复杂对象和数组的序列化和反序列化。

注意

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 调用依赖于保持活动状态的中心的异步方法时请使用 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 连接,或者 null 连接未与 HTTP 请求关联。 对于 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 提供 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 Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnDisconnectedAsync(exception);
}

警告

安全警告:如果 SignalR 服务器或客户端版本为 ASP.NET Core 2.2 或更早版本,则公开 ConnectionId 可能会导致恶意的模拟行为。

处理错误

在中心方法中引发的异常会发送到调用方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript 承诺。 当客户端收到一个错误,其中处理程序附加到承诺时 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 CoreSignalR中的安全注意事项

如果具有确实要传播到客户端的异常状况,则可以使用 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();

向 ASP.NET Core应用添加SignalR功能时,通过调用UseSignalRStartup.Configure方法设置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 会处理参数和返回值中复杂对象和数组的序列化和反序列化。

注意

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 调用依赖于保持活动状态的中心的异步方法时请使用 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 连接,或者 null 连接未与 HTTP 请求关联。 对于 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 提供 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 Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnDisconnectedAsync(exception);
}

警告

安全警告:如果 SignalR 服务器或客户端版本为 ASP.NET Core 2.2 或更早版本,则公开 ConnectionId 可能会导致恶意的模拟行为。

处理错误

在中心方法中引发的异常会发送到调用方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript 承诺。 当客户端收到一个错误,并显示一个处理程序附加到承诺时 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 CoreSignalR中的安全注意事项

如果具有确实要传播到客户端的异常状况,则可以使用 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服务,请调用AddSignalRProgram.cs

var builder = WebApplication.CreateBuilder(args);

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

若要配置 SignalR 终结点,请调用 MapHub,另请在 Program.cs

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

app.Run();

创建和使用中心

通过声明继承自 Hub的类来创建中心。 将方法添加到 public 类,使其可从客户端调用:

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

注意

中心是 暂时性的

  • 不要将状态存储在中心类的属性中。 每个中心方法调用在新的中心实例上执行。
  • 调用依赖于保持活动状态的中心的异步方法时请使用 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 连接,或者 null 如果连接未与 HTTP 请求关联。 对于 HTTP 连接,请使用此方法获取 HTTP 标头和查询字符串等信息。
Abort 中止连接。

客户端对象

Hub 类包含一个 Clients 属性,其中包含用于服务器和客户端之间通信的以下属性:

属性 说明
All 对所有连接的客户端调用方法
Caller 对调用了中心方法的客户端调用方法
Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

Hub.Clients 还包含以下方法:

方法 说明
AllExcept 对所有连接的客户端调用方法(指定连接除外)
Client 对连接的一个特定客户端调用方法
Clients 对连接的多个特定客户端调用方法
Group 对指定组中的所有连接调用方法
GroupExcept 对指定组中的所有连接调用方法(指定连接除外)
Groups 对多个连接组调用方法
OthersInGroup 对一个连接组调用方法(不包括调用了中心方法的客户端)
Single 对连接的一个特定客户端调用方法
User 对与一个特定用户关联的所有连接调用方法
Users 对与多个指定用户关联的所有连接调用方法

以上表中的每个属性或方法都返回具有 SendAsync 方法的对象。 该方法 SendAsync 接收要调用的客户端方法的名称和任何参数。

该方法返回 Single 的对象还包含一个 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 化类 Hub<T>。 In the following example, the ChatHub client method has been extracted out into an interface called 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 方法中调用Single(...)Clients属性:

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

注意

使用 InvokeAsync Hub 方法需要将 MaximumParallelInvocationsPerClient 选项设置为大于 1 的值。

这将在以后的版本中解决。 有关详细信息,请参阅 支持从客户端调用返回值

第二种方法是在以下实例IHubContext<T>上调用Single(...)

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Single(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.Single(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;
});

注意

客户端结果不适用于 Azure SignalR 服务。

这将在以后的版本中解决。 有关详细信息,请参阅 支持从客户端调用返回值

更改中心方法的名称

默认情况下,服务器中心方法名称是 .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] 在参数上实现 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 Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnDisconnectedAsync(exception);
}

处理错误

中心方法中引发的异常将发送到调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript 承诺。 客户端可以将处理程序附加到catch返回的承诺,或者用于asyncawaittry/catch/处理异常:

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 CoreSignalR中的安全注意事项

如果必须将异常条件传播到客户端,请使用该 HubException 类。 如果在中心方法中引发 aHubException,SignalR则向客户端发送整个异常消息,未修改:

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

注意

SignalR 仅将异常的 Message 属性发送到客户端。 异常中的堆栈跟踪和其他属性不可供客户端使用。

其他资源