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

作者:Rachel AppelKevin 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 方法接收要调用的客户端方法的名称和任何参数。

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,第一种方法是在中心方法中对 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 (或 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);
}

无需在 OnDisconnectedAsync 中调用 RemoveFromGroupAsync,系统会自动为你进行处理。

处理错误

在中心方法中引发的异常会发送到调用方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript Promise。 客户端可以将 catch 处理程序附加到返回的承诺,或使用和 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 负责使实时客户端到服务器和服务器到客户端通信成为可能。

配置 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 方法接收要调用的客户端方法的名称和任何参数。

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,第一种方法是在中心方法中对 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 处理程序附加到返回的承诺,或使用和 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 负责使实时客户端到服务器和服务器到客户端通信成为可能。

配置 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 提供 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 处理程序附加到返回的承诺,或使用和 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 负责使实时客户端到服务器和服务器到客户端通信成为可能。

配置 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 提供 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 收到处理程序附加到承诺的错误时,它会作为 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 负责使实时客户端到服务器和服务器到客户端通信成为可能。

配置 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 提供 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 收到处理程序附加到承诺的错误时,它会作为 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 属性发送到客户端。 异常中的堆栈跟踪和其他属性不可供客户端使用。

其他资源