通过


在SignalR中使用适用于 ASP.NET Core 的 Hubs

作者: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终结点,请调用MapHub,并在Program.cs中执行:

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

app.Run();

Note

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);
}

Note

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 请勿通过依赖注入直接实例化集线器。 若要从应用程序中的其他位置向客户端发送消息,请使用 IHubContext
  • 当调用依赖于集线器保持活动状态的异步方法时,请使用 await。 例如,如果在没有 Clients.All.SendAsync(...) 的情况下进行调用,则 await 这类方法会失败,并且中心方法会在 SendAsync 完成之前完成。

上下文对象

Hub 类具有一个 Context 属性,该属性包含具有连接相关信息的以下属性:

Property Description
ConnectionId 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。
UserIdentifier 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimTypes.NameIdentifier 中的 ClaimsPrincipal 作为用户标识符。
User 获取与当前用户关联的 ClaimsPrincipal
Items 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,并在连接的不同时段调用中心方法时持久保存。
Features 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。
ConnectionAborted 获取一个 CancellationToken,当连接中止时会发出通知。

Hub.Context 还包含以下方法:

Method Description
GetHttpContext 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。
Abort 中止连接。

客户端对象

Hub 类具有一个 Clients 属性,该属性包含适用于服务器与客户端之间的通信的以下属性:

Property Description
All 对所有连接的客户端调用方法
Caller 在调用中心方法的客户端上调用一个方法
Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

Hub.Clients 还包含以下方法:

Method Description
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 类强类型化为 Hub<T>。 在下面的示例中,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

Note

Async 后缀不会从方法名称中去除。 除非使用 .on('MyMethodAsync') 定义客户端方法,否则不要使用 MyMethodAsync 作为名称。

客户端结果

除了对客户端进行调用外,服务器还可以从客户端请求结果。 这要求服务器使用 ISingleClientProxy.InvokeAsync,并且客户端从其 .On 处理程序返回结果。

有两种方法可以在服务器上使用 API,第一种方法是在中心方法中对 Client(...) 属性调用 CallerClients

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

第二种方法是对 Client(...) 的实例调用 IHubContext<T>

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");
});

更改 Hub 方法的名称

默认情况下,服务器中心方法名称是 .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);
    }
}

Note

此功能使用 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);
}

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

处理错误

在中心服务器方法中引发的异常会发送到调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript Promise。 客户端可以将 catch 处理程序附加到返回的 Promise,或使用 try/catchasync/await 来处理异常。

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

当中心引发异常时,连接不会关闭。 默认情况下,SignalR 将向客户端返回一般错误消息,如以下示例所示:

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

意外异常通常包含敏感信息,例如在数据库连接失败时触发的异常中会包含数据库服务器的名称。 作为安全措施,SignalR 在默认情况下不会公开这些详细错误消息。 有关为何禁止显示异常详细信息的详细信息,请参阅 ASP.NET Core SignalR 中的安全注意事项一文。

如果必须将异常情况传播到客户端,请使用 HubException 类。 如果在中心方法中引发 HubException,SignalR会将整个异常消息(未修改)发送到客户端

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

Note

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终结点,请调用MapHub,并在Program.cs中执行:

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

app.Run();

Note

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);
}

Note

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 请勿通过依赖注入直接实例化Hub。 若要从应用程序中的其他位置向客户端发送消息,请使用 IHubContext
  • 调用依赖于保持集线器活动状态的异步方法时请使用 await。 例如,如果在没有 Clients.All.SendAsync(...) 的情况下进行调用,则 await 这类方法会失败,并且中心方法会在 SendAsync 完成之前完成。

上下文对象

Hub 类具有一个 Context 属性,该属性包含具有连接相关信息的以下属性:

Property Description
ConnectionId 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。
UserIdentifier 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimTypes.NameIdentifier 中的 ClaimsPrincipal 作为用户标识符。
User 获取与当前用户关联的 ClaimsPrincipal
Items 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。
Features 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。
ConnectionAborted 获取一个 CancellationToken 对象,当连接被中止时,它会发出通知。

Hub.Context 还包含以下方法:

Method Description
GetHttpContext 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。
Abort 中止连接。

客户端对象

Hub 类具有一个 Clients 属性,该属性包含适用于服务器与客户端之间的通信的以下属性:

Property Description
All 对所有已经连接的客户端调用方法
Caller 在调用中心方法的客户端上调用方法
Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

Hub.Clients 还包含以下方法:

Method Description
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 类强类型化为 Hub<T>。 在下面的示例中,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

Note

Async 后缀不会从方法名称中去除。 除非使用 .on('MyMethodAsync') 定义客户端方法,否则不要使用 MyMethodAsync 作为名称。

客户端结果

除了对客户端进行调用外,服务器还可以从客户端请求结果。 这要求服务器使用 ISingleClientProxy.InvokeAsync,并且客户端从其 .On 处理程序返回结果。

有两种方法可以在服务器上使用 API,第一种方法是在中心方法中对 Client(...) 属性调用 CallerClients

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

第二种方法是对 Client(...) 的实例调用 IHubContext<T>

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");
});

更改 Hub 方法的名称

默认情况下,服务器中心方法名称是 .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);
    }
}

Note

此功能使用 IServiceProviderIsService,通过 DI 实现来实现(可选)。 如果应用的 DI 容器不支持此功能,则不支持将服务注入中心方法。

为连接处理事件

SignalR Hubs API 提供 OnConnectedAsyncOnDisconnectedAsync 虚拟方法来管理和跟踪连接。 替代 OnConnectedAsync 虚拟方法可在客户端连接到中心时执行操作(例如将它添加到组):

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

替代 OnDisconnectedAsync 虚拟方法可在客户端断开连接时执行操作。 如果客户端有意断开连接(例如通过调用 connection.stop()),则 exception 参数将设置为 null。 但是,如果客户端由于错误(例如网络故障)而断开连接,则 exception 参数将包含描述故障的异常:

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

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

处理错误

在集线器方法中引发的异常会发送到调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript Promise。 客户端可以将 catch 处理程序附加到返回的Promise对象,或使用 try/catchasync/await 来处理异常:

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

当中心引发异常时,连接不会关闭。 默认情况下,SignalR 将向客户端返回一般错误消息,如以下示例所示:

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

意外异常通常包含敏感信息,例如在数据库连接失败时触发的异常中会包含数据库服务器的名称。 作为安全措施,SignalR 在默认情况下不会公开这些详细错误消息。 有关为何禁止显示异常详细信息的详细信息,请参阅 ASP.NET Core SignalR 中的安全注意事项一文。

如果必须将异常情况传播到客户端,请使用 HubException 类。 如果在集线器方法中引发 HubException,则 SignalR 会将整个异常消息发送到客户端(未修改):

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

Note

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终结点,请调用MapHub,并在Program.cs中执行:

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

app.Run();

Note

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);
}

Note

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 请勿通过依赖注入直接实例化Hub。 若要从应用程序中的其他位置向客户端发送消息,请使用 IHubContext
  • 调用依赖于保持集线器活动状态的异步方法时请使用 await。 例如,如果在没有 Clients.All.SendAsync(...) 的情况下进行调用,则 await 这类方法会失败,并且中心方法会在 SendAsync 完成之前完成。

上下文对象

Hub 类具有一个 Context 属性,该属性包含具有连接相关信息的以下属性:

Property Description
ConnectionId 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。
UserIdentifier 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimTypes.NameIdentifier 中的 ClaimsPrincipal 作为用户标识符。
User 获取与当前用户关联的 ClaimsPrincipal
Items 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。
Features 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。
ConnectionAborted 获取一个 CancellationToken 对象,当连接被中止时,它会发出通知。

Hub.Context 还包含以下方法:

Method Description
GetHttpContext 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。
Abort 中止连接。

客户端对象

Hub 类具有一个 Clients 属性,该属性包含适用于服务器与客户端之间的通信的以下属性:

Property Description
All 对所有已经连接的客户端调用方法
Caller 在调用中心方法的客户端上调用方法
Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

Hub.Clients 还包含以下方法:

Method Description
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>。 在下面的示例中,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

Note

Async 后缀不会从方法名称中去除。 除非使用 .on('MyMethodAsync') 定义客户端方法,否则不要使用 MyMethodAsync 作为名称。

更改 Hub 方法的名称

默认情况下,服务器中心方法名称是 .NET 方法的名称。 若要更改特定方法的此默认行为,请使用 HubMethodName 属性。 调用方法时,客户端应使用此名称,而不是 .NET 方法名称:

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

为连接处理事件

SignalR Hubs API 提供 OnConnectedAsyncOnDisconnectedAsync 虚拟方法来管理和跟踪连接。 替代 OnConnectedAsync 虚拟方法可在客户端连接到中心时执行操作(例如将它添加到组):

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

替代 OnDisconnectedAsync 虚拟方法可在客户端断开连接时执行操作。 如果客户端有意断开连接(例如通过调用 connection.stop()),则 exception 参数将设置为 null。 但是,如果客户端由于错误(例如网络故障)而断开连接,则 exception 参数将包含描述故障的异常:

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

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

处理错误

在集线器方法中引发的异常会发送到调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript Promise。 客户端可以将 catch 处理程序附加到返回的Promise对象,或使用 try/catchasync/await 来处理异常:

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

当中心引发异常时,连接不会关闭。 默认情况下,SignalR 将向客户端返回一般错误消息,如以下示例所示:

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

意外异常通常包含敏感信息,例如在数据库连接失败时触发的异常中会包含数据库服务器的名称。 作为安全措施,SignalR 在默认情况下不会公开这些详细错误消息。 有关为何禁止显示异常详细信息的详细信息,请参阅 ASP.NET Core SignalR 中的安全注意事项一文。

如果必须将异常情况传播到客户端,请使用 HubException 类。 如果在集线器方法中引发 HubException,则 SignalR 会将整个异常消息发送到客户端(未修改):

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

Note

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

其他资源

作者:Rachel AppelKevin Griffin

查看或下载示例代码如何下载

什么是 SignalR 集线器

SignalR 中心 API 使连接的客户端可以在服务器上调用方法,从而促进实时通信。 服务器定义由客户端调用的方法,客户端定义由服务器调用的方法。 SignalR 还支持客户端到客户端的间接通信,始终由 SignalR 中心调解,从而允许在单个客户端、组之间发送消息或发送到所有连接的客户端。 SignalR 负责使实时客户端到服务器和服务器到客户端通信成为可能。

配置 SignalR 集线器

SignalR 中间件需要一些服务,这些服务通过调用 AddSignalR 进行配置:

services.AddSignalR();

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

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

Note

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 会处理参数和返回值中复杂对象和数组的序列化和反序列化。

Note

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 请勿通过依赖注入直接实例化Hub。 若要从应用程序中的其他位置向客户端发送消息,请使用 IHubContext
  • 调用依赖于保持集线器活动状态的异步方法时请使用 await。 例如,如果在没有 Clients.All.SendAsync(...) 的情况下进行调用,则 await 这类方法会失败,并且中心方法会在 SendAsync 完成之前完成。

上下文对象

Hub 类具有一个 Context 属性,该属性包含具有连接相关信息的以下属性:

Property Description
ConnectionId 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。
UserIdentifier 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimTypes.NameIdentifier 中的 ClaimsPrincipal 作为用户标识符。
User 获取与当前用户关联的 ClaimsPrincipal
Items 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。
Features 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。
ConnectionAborted 获取一个 CancellationToken 对象,当连接被中止时,它会发出通知。

Hub.Context 还包含以下方法:

Method Description
GetHttpContext 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。
Abort 中止连接。

客户端对象

Hub 类具有一个 Clients 属性,该属性包含适用于服务器与客户端之间的通信的以下属性:

Property Description
All 对所有已经连接的客户端调用方法
Caller 在调用中心方法的客户端上调用方法
Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

Hub.Clients 还包含以下方法:

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

以上表中的每个属性或方法都返回具有 SendAsync 方法的对象。 通过 SendAsync 方法可以提供要调用的客户端方法的名称和参数。

向客户端发送消息

若要对特定客户端发出调用,请使用 Clients 对象的属性。 在下面的示例中,有三个 Hub 方法:

  • 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 的替代方法是使用 HubHub<T> 设为强类型。 在下面的示例中,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();
}

Note

Async 后缀不会从方法名称中去除。 除非客户端方法使用 .on('MyMethodAsync') 进行定义,否则不应使用 MyMethodAsync 作为名称。

更改 Hub 方法的名称

默认情况下,服务器中心方法名称是 .NET 方法的名称。 但是,可以使用 HubMethodName 属性更改此默认设置,并手动指定方法的名称。 调用方法时,客户端应使用此名称,而不是 .NET 方法名称:

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

为连接处理事件

SignalR Hubs API 提供 OnConnectedAsyncOnDisconnectedAsync 虚拟方法来管理和跟踪连接。 重写 OnConnectedAsync 虚拟方法,以便在客户端连接到 Hub 时执行操作,例如将其添加到某个组:

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);
}

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

Warning

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

处理错误

在 Hub 方法中抛出的异常会发送给调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript Promise。 当客户端收到一个包含处理程序的 Promise 错误时,该错误会作为 JavaScript Error 对象被调用和传递:

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

如果 Hub 出现异常,则连接不会关闭。 默认情况下,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!");
}

Note

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

其他资源

作者:Rachel AppelKevin Griffin

查看或下载示例代码如何下载

什么是 SignalR 集线器

SignalR 中心 API 使连接的客户端可以在服务器上调用方法,从而促进实时通信。 服务器定义由客户端调用的方法,客户端定义由服务器调用的方法。 SignalR 还支持客户端到客户端的间接通信,始终由 SignalR 中心调解,从而允许在单个客户端、组之间发送消息或发送到所有连接的客户端。 SignalR 负责使实时客户端到服务器和服务器到客户端通信成为可能。

配置 SignalR 集线器

SignalR 中间件需要一些服务,这些服务通过调用 AddSignalR 进行配置:

services.AddSignalR();

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

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 会处理参数和返回值中复杂对象和数组的序列化和反序列化。

Note

中心是暂时性的:

  • 请勿将状态存储在中心类的属性中。 每个中心方法调用都在新的中心实例上执行。
  • 请勿通过依赖注入直接实例化Hub。 若要从应用程序中的其他位置向客户端发送消息,请使用 IHubContext
  • 调用依赖于保持集线器活动状态的异步方法时请使用 await。 例如,如果在没有 Clients.All.SendAsync(...) 的情况下进行调用,则 await 这类方法会失败,并且中心方法会在 SendAsync 完成之前完成。

上下文对象

Hub 类具有一个 Context 属性,该属性包含具有连接相关信息的以下属性:

Property Description
ConnectionId 获取连接的唯一 ID(由 SignalR 分配)。 每个连接有一个连接 ID。
UserIdentifier 获取用户标识符。 默认情况下,SignalR 使用与连接关联的 ClaimTypes.NameIdentifier 中的 ClaimsPrincipal 作为用户标识符。
User 获取与当前用户关联的 ClaimsPrincipal
Items 获取可用于在此连接范围内共享数据的键/值集合。 数据可以存储在此集合中,会在不同的中心方法调用间为连接持久保存。
Features 获取连接上可用的功能的集合。 目前,在大多数情况下不需要此集合,因此未对其进行详细记录。
ConnectionAborted 获取一个 CancellationToken 对象,当连接被中止时,它会发出通知。

Hub.Context 还包含以下方法:

Method Description
GetHttpContext 返回连接的 HttpContext,如果连接不与 HTTP 请求关联,则返回 null。 对于 HTTP 连接,可以使用此方法获取 HTTP 标头和查询字符串等信息。
Abort 中止连接。

客户端对象

Hub 类具有一个 Clients 属性,该属性包含适用于服务器与客户端之间的通信的以下属性:

Property Description
All 对所有已经连接的客户端调用方法
Caller 在调用中心方法的客户端上调用方法
Others 对所有连接的客户端调用方法(调用了方法的客户端除外)

Hub.Clients 还包含以下方法:

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

以上表中的每个属性或方法都返回具有 SendAsync 方法的对象。 通过 SendAsync 方法可以提供要调用的客户端方法的名称和参数。

向客户端发送消息

若要对特定客户端发出调用,请使用 Clients 对象的属性。 在下面的示例中,有三个 Hub 方法:

  • 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 的替代方法是使用 HubHub<T> 设为强类型。 在下面的示例中,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();
}

Note

Async 后缀不会从方法名称中去除。 除非客户端方法使用 .on('MyMethodAsync') 进行定义,否则不应使用 MyMethodAsync 作为名称。

更改 Hub 方法的名称

默认情况下,服务器中心方法名称是 .NET 方法的名称。 但是,可以使用 HubMethodName 属性更改此默认设置,并手动指定方法的名称。 调用方法时,客户端应使用此名称,而不是 .NET 方法名称:

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

为连接处理事件

SignalR Hubs API 提供 OnConnectedAsyncOnDisconnectedAsync 虚拟方法来管理和跟踪连接。 重写 OnConnectedAsync 虚拟方法,以便在客户端连接到 Hub 时执行操作,例如将其添加到某个组:

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);
}

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

Warning

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

处理错误

在 Hub 方法中抛出的异常会发送给调用该方法的客户端。 在 JavaScript 客户端上,invoke 方法会返回 JavaScript Promise。 当客户端收到一个包含处理程序的 Promise 错误时,该错误会作为 JavaScript Error 对象被调用和传递:

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

如果 Hub 出现异常,则连接不会关闭。 默认情况下,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!");
}

Note

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

其他资源