注意
此版本不是本文的最新版本。 有关当前版本,请参阅 本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文的 .NET 9 版本。
本文介绍如何配置和管理 SignalR 应用中的 Blazor 连接。
有关 ASP.NET Core SignalR 配置的常规指南,请参阅文档的 ASP.NET Core SignalR 概述区域中的主题,特别是 ASP.NET Core SignalR 配置。
服务器端应用使用 ASP.NET Core SignalR 与浏览器进行通信。 SignalR 的托管和缩放条件适用于服务器端应用。
由于低延迟、可靠性和安全性,使用 WebSocket 作为 SignalR 传输时,Blazor 的效果最佳。 当 WebSocket 不可用时,或在将应用显式配置为使用长轮询时,SignalR 将使用长轮询。
使用有状态重新连接的 Azure SignalR 服务
具有 SDK SignalR 或更高版本的 Azure 服务支持 SignalR 有状态重新连接 (WithStatefulReconnect)。
交互式服务器组件的 WebSocket 压缩
默认情况下,交互式服务器组件:
为 WebSocket 连接启用压缩。 DisableWebSocketCompression (默认值:
false
)控制 WebSocket 压缩。"采用将
frame-ancestors
内容安全策略(CSP)指令设置为'self'
的方法,该指令为默认设置,仅允许在启用压缩或提供 WebSocket 上下文配置时,将应用嵌入服务该应用的源页面的<iframe>
中。"
默认的frame-ancestors
CSP 的值可以通过设置 ContentSecurityFrameAncestorsPolicy为null
来更改,如果您想以集中方式配置 CSP或'none'
实现更严格的策略。 以集中方式管理 frame-ancestors
CSP 时,每当呈现第一个文档时都必须审慎应用策略。 我们不建议完全删除策略,因为它会使应用容易受到攻击。 有关详细信息,请参阅 针对 ASP.NET Core Blazor 和 MDN CSP 指南强制实施内容安全策略。
使用 ConfigureWebSocketAcceptContext 为服务器组件使用的 WebSocket 连接配置 WebSocketAcceptContext。 默认情况下,会应用一个策略,该策略启用压缩并为ContentSecurityFrameAncestorsPolicy中定义的帧上级设置CSP。
用法示例:
通过将 DisableWebSocketCompression 设为 true
来禁用压缩,这可以减少应用受攻击的风险,但可能会导致性能降低:
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(o => o.DisableWebSocketCompression = true)
启用压缩后,配置一个更严格的 frame-ancestors
CSP,值为 'none'
(需要单引号),该设置允许 WebSocket 压缩,但会阻止浏览器将应用嵌入到 <iframe>
:
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")
启用压缩后,通过将 frame-ancestors
设置为 ContentSecurityFrameAncestorsPolicy 来移除 null
CSP。 此方案仅适用于以集中方式设置 CSP 的应用:
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)
重要
浏览器使用最严格的策略指令值应用来自多个 CSP 标头的 CSP 指令。 因此,开发人员无法故意或错误地添加比 frame-ancestors
更弱的 'self'
策略。
传递给 ContentSecurityFrameAncestorsPolicy 的字符串值需要单引号:
不支持的值:none
、self
支持的值:'none'
、'self'
其他选项包括指定一个或多个主机源和方案源。
有关安全影响,请参阅 ASP.NET Core Blazor 交互式服务器端呈现的威胁缓解指南。 有关详细信息,请参阅针对 ASP.NET Core Blazor和 CSP 强制实施内容安全策略: frame-ancestors
(MDN 文档)。
禁止热重载时的响应压缩
使用热重载时,请在 Development
环境中禁用响应压缩中间件。 无论是否使用项目模板中的默认代码,始终首先在请求处理管道中调用 UseResponseCompression。
在 Program
文件中:
if (!app.Environment.IsDevelopment())
{
app.UseResponseCompression();
}
用于身份验证的客户端侧 SignalR 跨源协商
本部分介绍如何将 SignalR 的基础客户端配置为发送凭据(如 Cookie 或 HTTP 身份验证标头)。
使用 SetBrowserRequestCredentials 在跨源 fetch
请求中设置 Include。
IncludeRequestCredentialsMessageHandler.cs
:
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;
public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return base.SendAsync(request, cancellationToken);
}
}
在构建中心连接的位置,将 HttpMessageHandler 分配给 HttpMessageHandlerFactory 选项:
private HubConnectionBuilder? hubConnection;
...
hubConnection = new HubConnectionBuilder()
.WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
{
options.HttpMessageHandlerFactory = innerHandler =>
new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
}).Build();
前面的示例将中心连接 URL 配置为位于 /chathub
的绝对 URI 地址。 该 URI 也可以通过字符串来设置,例如 https://signalr.example.com
,或者通过配置进行设置。 Navigation
是一个注入的 NavigationManager。
有关详细信息,请参阅 ASP.NET Core SignalR 配置。
客户端渲染
如果已配置预呈现,则会在建立到服务器的客户端连接之前进行预呈现。 有关详细信息,请参阅 Prerender ASP.NET Core Razor 组件。
如果已配置预呈现,则会在建立到服务器的客户端连接之前进行预呈现。 有关详细信息,请参阅以下文章:
预呈现状态大小和 SignalR 消息大小限制
大型预呈现状态大小可能超过 Blazor线路 SignalR 消息大小限制,这会导致以下结果:
- SignalR 线路初始化失败,客户端上出现错误:Circuit host not initialized.
- 当线路发生故障时,客户端上显示重新连接 UI。 无法进行恢复。
若要解决此问题,请使用以下任一方法:
- 减少要置于预呈现状态的数据量。
- 增加 SignalR 消息大小限制。 警告:增加限制可能会增加拒绝服务 (DoS) 攻击风险。
其他客户端侧资源
- 保护 SignalR 中心
- ASP.NET Core SignalR 概述
- ASP.NET Core SignalR 配置
- Blazor 示例 GitHub 存储库 (
dotnet/blazor-samples
)(下载方法)
将会话亲和性(粘滞会话)用于服务器端 webfarm 托管
使用多个后端服务器时,应用必须实现会话亲和性,也称为粘滞会话。 会话亲和性可确保客户端的线路在断开连接时重新连接到同一服务器,这一点很重要,因为客户端状态仅在首次建立客户端线路的服务器内存中保留。
以下错误由未在 webfarm 中启用会话亲和性的应用引发:
Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.
有关会话亲和性与 Azure 应用服务托管的更多信息,请参阅托管和部署 ASP.NET Core 服务器端 Blazor 应用。
Azure SignalR 服务
可选的 Azure SignalR 服务可与应用的 SignalR 中心配合使用,以将服务器端应用纵向扩展到大量并发连接。 此外, 服务的全球覆盖和高性能数据中心可帮助显著减少由于地理位置造成的延迟。
Azure 应用服务或 Azure 容器应用中托管的 Blazor 应用不需要该服务,但在其他托管环境中可能会有所帮助:
- 为了便于连接横向扩展。
- 处理全局分发。
有关详细信息,请参阅托管和部署 ASP.NET Core 服务器端 Blazor 应用。
服务器端线路处理程序选项
使用 CircuitOptions 配置线路。 查看引用源中的默认值。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
使用 AddInteractiveServerComponents 的选项委托读取或设置 Program
文件中的选项。 {OPTION}
占位符代表选项,{VALUE}
占位符是值。
在 Program
文件中:
builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
options.{OPTION} = {VALUE};
});
使用 AddServerSideBlazor 的选项委托读取或设置 Program
文件中的选项。 {OPTION}
占位符代表选项,{VALUE}
占位符是值。
在 Program
文件中:
builder.Services.AddServerSideBlazor(options =>
{
options.{OPTION} = {VALUE};
});
使用 AddServerSideBlazor 的选项委托读取或设置 Startup.ConfigureServices
中的选项。 {OPTION}
占位符代表选项,{VALUE}
占位符是值。
在 Startup.cs
的 Startup.ConfigureServices
中:
services.AddServerSideBlazor(options =>
{
options.{OPTION} = {VALUE};
});
若要配置 HubConnectionContext,请结合使用 HubConnectionContextOptions 和 AddHubOptions。 查看引用源中的中心连接上下文选项的默认值。 有关 SignalR 文档中的选项说明,请参阅 ASP.NET Core SignalR 配置。 {OPTION}
占位符代表选项,{VALUE}
占位符是值。
备注
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
在 Program
文件中:
builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
options.{OPTION} = {VALUE};
});
在 Program
文件中:
builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
options.{OPTION} = {VALUE};
});
在 Startup.cs
的 Startup.ConfigureServices
中:
services.AddServerSideBlazor().AddHubOptions(options =>
{
options.{OPTION} = {VALUE};
});
警告
MaximumReceiveMessageSize 的默认值为 32 KB。 提高此值可能会增加拒绝服务(DoS)攻击风险。
Blazor 依赖于 MaximumParallelInvocationsPerClient,后者设置为 1,这是默认值。 有关详细信息,请参阅 MaximumParallelInvocationsPerClient > 1 中断 Blazor Server 模式下的文件上传 (dotnet/aspnetcore
#53951)。
有关内存管理的信息,请参阅 管理已部署 ASP.NET 核心服务器端 Blazor 应用中的内存。
Blazor 集线器选项
配置 MapBlazorHub 选项以控制 Blazor 中心的 HttpConnectionDispatcherOptions。 查看引用源中的中心连接调度程序选项的默认值。 {OPTION}
占位符代表选项,{VALUE}
占位符是值。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
在应用的 Program
文件中调用 app.MapRazorComponents
之后调用 app.MapBlazorHub
:
app.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});
使用 MapBlazorHub 配置 AddInteractiveServerRenderMode 使用的集线器失败,并出现 AmbiguousMatchException
:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
要解决面向 .NET 8 的应用的问题,请使用 Blazor 方法为自定义配置的 WithOrder 中心提供更高的优先级:
app.MapBlazorHub(options =>
{
options.CloseOnAuthenticationExpiration = true;
}).WithOrder(-1);
有关更多信息,请参阅以下资源:
在应用的 Program
文件中为 app.MapBlazorHub
提供选项:
app.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});
在终结点路由配置中向 app.MapBlazorHub
提供选项:
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});
...
});
最大接收消息大小
本部分仅适用于实现 SignalR 的项目。
中心方法允许的最大传入 SignalR 消息大小限制受 HubOptions.MaximumReceiveMessageSize 限制(默认值:32 KB)。 SignalR 消息大于 MaximumReceiveMessageSize 时会引发错误。 框架对从中心到客户端的 SignalR 消息大小不施加限制。
如果未将 SignalR 日志记录设置为调试或跟踪,则消息大小错误仅显示在浏览器的开发人员工具控制台中:
错误:连接断开,出现错误“错误: 服务器关闭时返回错误: 连接因错误而关闭”。
将 SignalR 服务器端日志记录设置为调试或跟踪时,服务器端日志记录会针对消息大小错误显示 InvalidDataException。
appsettings.Development.json
:
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
...
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}
错误:
System.IO.InvalidDataException:消息大小超出最大限制,为 32768 B。 消息大小可以在 AddHubOptions 中配置。
一种方法是通过在 MaximumReceiveMessageSize 文件中设置 Program
来增加上限。 以下示例将最大接收消息大小设置为 64 KB:
builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
提高 SignalR 传入消息大小限制的代价是需要使用更多的服务器资源,这将增加拒绝服务 (DoS) 攻击的风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。
读取大型有效负载的更好方法是将内容分成较小块进行发送,并将有效负载处理为 Stream。 在读取大型 JavaScript (JS) 互操作 JSON 有效负载时,或者 JS 互操作数据以原始字节形式提供时,可以使用此方法。 有关演示如何使用类似于 InputFile
组件的方法在服务器端应用中发送大型二进制有效负载的示例,请参阅二进制文件提交示例应用和 BlazorInputLargeTextArea
组件示例。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉菜单。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
通过 SignalR 处理大型有效负载的表单也可以直接使用流式传输 JS 互操作。 有关详细信息,请参阅在 ASP.NET Core Blazor 中从 JavaScript 函数调用 .NET 方法。 有关将 <textarea>
数据流式传输到服务器的表单示例,请参阅排查 ASP.NET Core Blazor 表单问题。
一种方法是通过在 MaximumReceiveMessageSize 文件中设置 Program
来增加上限。 以下示例将最大接收消息大小设置为 64 KB:
builder.Services.AddServerSideBlazor()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
提高 SignalR 传入消息大小限制的代价是需要使用更多的服务器资源,这将增加拒绝服务 (DoS) 攻击的风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。
对于读取大型有效负载,更好的方法是将内容分成较小的块发送,并将其作为 Stream 处理。 在读取大型 JavaScript (JS) 互操作 JSON 有效负载时,或者 JS 互操作数据以原始字节形式提供时,可以使用此方法。 有关在 Blazor Server 中使用与 InputFile
组件类似的方法发送大型二进制有效负载的示例,请参阅 二进制提交示例应用 和 BlazorInputLargeTextArea
组件示例。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
通过 SignalR 处理大型有效负载的表单也可以直接使用流式传输 JS 互操作。 有关详细信息,请参阅在 ASP.NET Core Blazor 中从 JavaScript 函数调用 .NET 方法。 有关在 Blazor Server 应用中流式传输 <textarea>
数据的表单示例,请参阅排查 ASP.NET Core Blazor 表单问题。
通过在 MaximumReceiveMessageSize 中设置 Startup.ConfigureServices
来提高限制:
services.AddServerSideBlazor()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
提高 SignalR 传入消息大小限制的代价是需要使用更多的服务器资源,这将增加拒绝服务 (DoS) 攻击的风险。 此外,如果将大量内容作为字符串或字节数组读入内存中,还会导致垃圾回收器的分配工作状况不佳,从而导致额外的性能损失。
开发用于传输大量数据的代码时,请考虑以下指南:
- 利用原生流式传输 JS 互操作支持来传输超过 SignalR 传入消息大小限制的数据:
- 常规提示:
- 不要在 JS 和 C# 代码中分配大型对象。
- 在进程完成或取消时释放已消耗的内存。
- 为了安全起见,请强制执行以下附加要求:
- 声明可以传递的最大文件或数据大小。
- 声明从客户端到服务器的最低上传速率。
- 在服务器收到数据后,数据可以:
- 暂时存储在内存缓冲区中,直到收集完所有数据段。
- 立即使用。 例如,在收到每个数据段时,数据可以立即存储到数据库中或写入磁盘。
- 将数据切成小块,然后按顺序发送数据段,直到服务器收到所有数据。
- 不要在 JS 和 C# 代码中分配大型对象。
- 发送或接收数据时,请勿长时间阻止主 UI 线程。
- 在进程完成或取消时释放已消耗的内存。
- 为了安全起见,请强制执行以下附加要求:
- 声明可以传递的最大文件或数据大小。
- 声明从客户端到服务器的最低上传速率。
- 当服务器接收数据后,数据可以被进行以下操作:
- 暂时存储在内存缓冲区中,直到收集完所有数据段。
- 立即食用。 例如,在收到每个数据段时,数据可以立即存储到数据库中或写入磁盘。
Blazor 服务器端集线器终结点路由配置
在 Program
文件中,调用 MapBlazorHub 以将 BlazorHub 映射到应用的默认路径。 Blazor 脚本 (blazor.*.js
) 自动指向 MapBlazorHub 创建的终结点。
在 UI 中反映服务器端连接状态
如果客户端检测到与服务器的连接丢失,则客户端尝试重新连接时向用户显示默认 UI:
如果重新连接失败,则指示用户重试或重新加载页面:
如果重新连接成功,用户状态通常会丢失。 可将自定义代码添加到任何组件,以跨连接失败保存和重新加载用户状态。 有关详细信息,请参阅 ASP.NET Core Blazor 状态管理。
若要创建跟踪重新连接状态的 UI 元素,下表描述了:
- 一组
components-reconnect-*
CSS 类(Css 类列),这些类由具有components-reconnect-modal
的id
的元素上的 Blazor 来设置或取消设置。 - 指示重新连接状态更改的
components-reconnect-state-changed
事件(事件列)。
CSS 类 | 事件 | 指示… |
---|---|---|
components-reconnect-show |
show |
连接已丢失。 客户端正在尝试重新连接。 系统会显示重新连接模式。 |
components-reconnect-hide |
hide |
重新为服务器建立活动连接。 重新连接模型已关闭。 |
components-reconnect-retrying |
retrying |
客户端正在尝试重新连接。 |
components-reconnect-failed |
failed |
重新连接失败,可能是由于网络故障引起的。 |
components-reconnect-rejected |
rejected |
已拒绝重新连接。 |
当 components-reconnect-state-changed
中的重新连接状态更改为 failed
时,在 JavaScript 中调用 Blazor.reconnect()
以尝试重新连接。
当重新连接状态更改为 rejected
时,服务器已连接但拒绝连接,用户在服务器上的状态丢失。 若要重新加载应用,请在 JavaScript 中调用 location.reload()
。 当出现以下情况时,可能会导致此连接状态:
- 服务器端线路发生故障。
- 客户端断开连接的时间足以使服务器删除用户的状态。 用户组件的实例已被处置。
- 服务器已重启,或者应用的工作进程被回收。
开发人员在重新连接模式元素上添加事件侦听器,以监视和响应重新连接状态更改,如以下示例所示:
const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed",
handleReconnectStateChanged);
function handleReconnectStateChanged(event) {
if (event.detail.state === "show") {
reconnectModal.showModal();
} else if (event.detail.state === "hide") {
reconnectModal.close();
} else if (event.detail.state === "failed") {
Blazor.reconnect();
} else if (event.detail.state === "rejected") {
location.reload();
}
}
一个 id
为 components-reconnect-max-retries
的元素会显示最大重新连接重试次数。
<span id="components-reconnect-max-retries"></span>
一个 id
为 components-reconnect-current-attempt
的元素会显示当前的重新连接尝试:
<span id="components-reconnect-current-attempt"></span>
一个 id
为 components-seconds-to-next-attempt
的元素会显示下一次重新连接尝试的秒数:
<span id="components-seconds-to-next-attempt"></span>
项目Blazor Web App模板包括一个ReconnectModal
组件(Components/Layout/ReconnectModal.razor
),其中包含可根据需要自定义的并置样式表和 JavaScript 文件(ReconnectModal.razor.css
, ReconnectModal.razor.js
)。 可以在 ASP.NET Core 引用源中或通过检查从 Blazor Web App 项目模板创建的应用来检查这些文件。 在 Visual Studio 中创建项目时,将组件添加到项目中,并将交互式呈现模式设置为“服务器”或“自动”,或者使用选项(默认值)或 --interactivity auto
.NET CLI --interactivity server
创建。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
若要自定义 UI,请在<body>
元素内容中定义一个包含id
和components-reconnect-modal
的元素。 以下示例将元素放置在 App
组件中。
App.razor
:
若要自定义 UI,请在 <body>
元素内容中定义一个 id
为 components-reconnect-modal
的单一元素。 以下示例将元素放置在主机页中。
Pages/_Host.cshtml
:
若要自定义 UI,请在 <body>
元素内容中定义一个 id
为 components-reconnect-modal
的单一元素。 以下示例将元素放置在布局页中。
Pages/_Layout.cshtml
:
若要自定义 UI,请在 <body>
元素内容中定义一个 id
为 components-reconnect-modal
的单一元素。 以下示例将元素放置在主机页中。
Pages/_Host.cshtml
:
<div id="components-reconnect-modal">
Connection lost.<br>Attempting to reconnect...
</div>
注意
如果应用呈现了多个 id
为 components-reconnect-modal
的元素,则只有第一个呈现的元素会接收 CSS 类更改以显示或隐藏元素。
将以下 CSS 样式添加到站点的样式表中。
wwwroot/app.css
:
wwwroot/css/site.css
:
#components-reconnect-modal {
display: none;
}
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
display: block;
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
text-align: center;
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
margin: 50px 50px;
position: fixed;
top: 0;
z-index: 10001;
}
下表介绍了 components-reconnect-modal
框架应用于 Blazor 元素的 CSS 类。
CSS 类 | 指示… |
---|---|
components-reconnect-show |
连接已丢失。 客户端正在尝试重新连接。 显示模式。 |
components-reconnect-hide |
将为服务器重新建立活动连接。 隐藏模式。 |
components-reconnect-failed |
重新连接失败,可能是由于网络故障引起的。 若要尝试重新连接,请在 JavaScript 中调用 window.Blazor.reconnect() 。 |
components-reconnect-rejected |
已拒绝重新连接。 已成功连接服务器,但被拒绝连接,服务器上的用户状态已丢失。 若要重新加载应用,请在 JavaScript 中调用 location.reload() 。 当出现以下情况时,可能会导致此连接状态:
|
通过在站点的 CSS 中为模式元素设置 transition-delay
属性,自定义重新连接 UI 出现之前的延迟。 以下示例将转换延迟从 500 毫秒(默认值)设置为 1000 毫秒(1 秒)。
wwwroot/app.css
:
wwwroot/css/site.css
:
#components-reconnect-modal {
transition: visibility 0s linear 1000ms;
}
若要显示当前的重新连接尝试,请定义一个 id
为 components-reconnect-current-attempt
的元素。 若要显示最大重新连接重试次数,请定义一个 id
为 components-reconnect-max-retries
的元素。 以下示例按照上一个示例,将这些元素放置在重新连接尝试模式元素内。
<div id="components-reconnect-modal">
There was a problem with the connection!
(Current reconnect attempt:
<span id="components-reconnect-current-attempt"></span> /
<span id="components-reconnect-max-retries"></span>)
</div>
当自定义重新连接模式出现时,它将呈现以下内容,其中包含重新连接尝试计数器:
连接出现问题! (当前重新连接尝试:1/8)
服务器端呈现
默认情况下,组件会在客户端与服务器建立连接之前在服务器上预呈现。 有关详细信息,请参阅 Prerender ASP.NET Core Razor 组件。
默认情况下,组件会在客户端与服务器建立连接之前在服务器上预呈现。 有关详细信息,请参阅 ASP.NET Core 中的组件标记帮助程序。
监视服务器端线路活动
使用 CircuitHandler 上的 CreateInboundActivityHandler 方法监视入站线路活动。 入站电路活动是指从浏览器发送到服务器的任何活动,例如 UI 事件或 JavaScript 到 .NET 的互操作调用。
例如,可以使用线路活动处理程序来检测客户端是否处于空闲状态并记录器线路 ID(Circuit.Id):
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;
public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
private Circuit? currentCircuit;
private readonly ILogger logger;
private readonly Timer timer;
public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger,
IOptions<IdleCircuitOptions> options)
{
timer = new Timer
{
Interval = options.Value.IdleTimeout.TotalMilliseconds,
AutoReset = false
};
timer.Elapsed += CircuitIdle;
this.logger = logger;
}
private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
{
logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
}
public override Task OnCircuitOpenedAsync(Circuit circuit,
CancellationToken cancellationToken)
{
currentCircuit = circuit;
return Task.CompletedTask;
}
public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
Func<CircuitInboundActivityContext, Task> next)
{
return context =>
{
timer.Stop();
timer.Start();
return next(context);
};
}
public void Dispose() => timer.Dispose();
}
public class IdleCircuitOptions
{
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}
public static class IdleCircuitHandlerServiceCollectionExtensions
{
public static IServiceCollection AddIdleCircuitHandler(
this IServiceCollection services,
Action<IdleCircuitOptions> configureOptions)
{
services.Configure(configureOptions);
services.AddIdleCircuitHandler();
return services;
}
public static IServiceCollection AddIdleCircuitHandler(
this IServiceCollection services)
{
services.AddScoped<CircuitHandler, IdleCircuitHandler>();
return services;
}
}
在 Program
文件中注册服务。 以下示例配置默认空闲超时为 5 分钟到 5 秒,以测试上述 IdleCircuitHandler
实现:
builder.Services.AddIdleCircuitHandler(options =>
options.IdleTimeout = TimeSpan.FromSeconds(5));
线路活动处理程序还提供了一种从其他非 Blazor 依赖项注入 (DI) 范围访问限定范围的 Blazor 服务的方法。 有关更多信息和示例,请参阅:
Blazor 启动
在 Blazor Web App 的 App.razor
文件中配置 Blazor 的 SignalR 回路的手动启动:
在 Pages/_Host.cshtml
文件 (Blazor Server) 中配置 Blazor 的 SignalR 回路的手动启动:
在 Pages/_Layout.cshtml
文件 (Blazor Server) 中配置 Blazor 的 SignalR 回路的手动启动:
在 Pages/_Host.cshtml
文件 (Blazor Server) 中配置 Blazor 的 SignalR 回路的手动启动:
- 将
autostart="false"
属性添加到<script>
脚本的blazor.*.js
标记中。 - 放置一个在加载
Blazor.start()
脚本后调用 Blazor 的脚本,将其置于结束的</body>
标记内。
禁用 autostart
时,应用中不依赖该回路的任何方面都能正常工作。 例如,客户端路由正常运行。 但是,在调用 Blazor.start()
之前,依赖于该回路的任何方面不会正常运行。 如果没有已建立的回路,应用行为是不可预测的。 例如,在回路断开连接时,组件方法无法执行。
有关详细信息,包括如何在文档准备就绪时初始化 Blazor,以及如何链接到 JS Promise
,请参阅 ASP.NET Core Blazor 启动。
在客户端上配置 SignalR 超时和 Keep-Alive
为客户端配置以下值:
-
withServerTimeout
:配置服务器超时(以毫秒为单位)。 如果此超时已过但未从服务器接收任何消息,连接将终止并出现错误。 默认超时值为 30 秒。 服务器超时应至少是分配给 Keep-Alive 间隔 (withKeepAliveInterval
) 的值的两倍。 withKeepAliveInterval
:配置保持活动间隔,以毫秒为单位(默认向服务器发送 ping 的间隔)。 使用此设置,服务器可以检测强行断开连接的情况,例如客户断开其计算机的网络连接。 此 ping 的发生频率最多与服务器 ping 的频率一样。 如果服务器每 5 秒 ping 一次,则分配的值低于5000
(5 秒)时,将会每 5 秒 ping 一次。 默认值为 15 秒。 Keep-Alive 间隔应小于或等于分配给服务器超时 (withServerTimeout
) 的值的一半。
以下 App.razor
文件 (Blazor Web App) 的示例显示了默认值的分配。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(30000).withKeepAliveInterval(15000);
}
}
});
</script>
下面是 Pages/_Host.cshtml
文件(Blazor Server,除 .NET 6 中的 ASP.NET Core 外的所有版本)或 Pages/_Layout.cshtml
文件(Blazor Server,.NET 6 中的 ASP.NET Core)的示例。
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(30000).withKeepAliveInterval(15000);
}
});
</script>
在前面的示例中,{BLAZOR SCRIPT}
占位符是 Blazor 脚本路径和文件名。 有关该脚本的位置和要使用的路径,请参阅 ASP.NET Core Blazor 项目结构。
在组件中创建中心连接时,在 ServerTimeout 上设置 KeepAliveInterval(默认值:30 秒)和 HubConnectionBuilder(默认值:15 秒)。 在生成 HubConnection 上设置 HandshakeTimeout(默认值:15 秒)。 以下示例显示了默认值的分配:
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(30))
.WithKeepAliveInterval(TimeSpan.FromSeconds(15))
.Build();
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
为客户端配置以下值:
serverTimeoutInMilliseconds
:服务器超时(以毫秒为单位)。 如果此超时已过但未从服务器接收任何消息,连接将终止并出现错误。 默认超时值为 30 秒。 服务器超时应至少是分配给 Keep-Alive 间隔 (keepAliveIntervalInMilliseconds
) 的值的两倍。keepAliveIntervalInMilliseconds
:ping 服务器时采用的默认间隔。 使用此设置,服务器可以检测强行断开连接的情况,例如客户断开其计算机的网络连接。 此 ping 的发生频率最多与服务器 ping 的频率一样。 如果服务器每 5 秒 ping 一次,则分配的值低于5000
(5 秒)时,将会每 5 秒 ping 一次。 默认值为 15 秒。 Keep-Alive 间隔应小于或等于分配给服务器超时 (serverTimeoutInMilliseconds
) 的值的一半。
下面是 Pages/_Host.cshtml
文件(Blazor Server,除 .NET 6 中的 ASP.NET Core 外的所有版本)或 Pages/_Layout.cshtml
文件(Blazor Server,.NET 6 中的 ASP.NET Core)的示例:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 30000;
c.keepAliveIntervalInMilliseconds = 15000;
builder.build = () => {
return c;
};
}
});
</script>
在前面的示例中,{BLAZOR SCRIPT}
占位符是 Blazor 脚本路径和文件名。 有关该脚本的位置和要使用的路径,请参阅 ASP.NET Core Blazor 项目结构。
在组件中创建中心连接时,在生成的 ServerTimeout 上设置 HandshakeTimeout(默认值:30 秒)、KeepAliveInterval(默认值:15 秒)和 HubConnection(默认值:15 秒)。 以下示例显示了默认值的分配:
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
更改服务器超时 (ServerTimeout) 或 Keep-Alive 间隔 (KeepAliveInterval) 的值时:
- 服务器超时应至少是分配给 Keep-Alive 间隔的值的两倍。
- Keep-Alive 间隔应小于或等于分配给服务器超时的值的一半。
有关详细信息,请参阅以下文章的“全球部署和连接失败”部分:
修改服务器端重新连接处理程序
可以针对自定义行为修改重新连接处理程序的线路连接事件,如:
- 在连接断开时通知用户。
- 在线路连接时(通过客户端)执行日志记录。
若要修改连接事件,请为以下连接更改注册回调:
- 使用
onConnectionDown
删除的连接。 - 已建立/重新建立的连接使用
onConnectionUp
。
必须同时指定 onConnectionDown
和 onConnectionUp
。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
}
});
</script>
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
});
</script>
在前面的示例中,{BLAZOR SCRIPT}
占位符是 Blazor 脚本路径和文件名。 有关该脚本的位置和要使用的路径,请参阅 ASP.NET Core Blazor 项目结构。
服务器端重新连接失败后自动刷新页面
默认的重新连接行为需要用户在重新连接失败后手动刷新页面。 但可使用自定义重新连接处理程序自动刷新页面:
App.razor
:
Pages/_Host.cshtml
:
<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>
在前面的示例中,{BLAZOR SCRIPT}
占位符是 Blazor 脚本路径和文件名。 有关该脚本的位置和要使用的路径,请参阅 ASP.NET Core Blazor 项目结构。
创建以下 wwwroot/boot.js
文件。
Blazor Web App:
(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');
const startReconnectionProcess = () => {
reconnectModal.style.display = 'block';
let isCanceled = false;
(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;
await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));
if (isCanceled) {
return;
}
try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected; reload the page.
location.reload();
return;
}
// Successfully reconnected to the server.
return;
} catch {
// Didn't reach the server; try again.
}
}
// Retried too many times; reload the page.
location.reload();
})();
return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};
let currentReconnectionProcess = null;
Blazor.start({
circuit: {
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
}
});
})();
Blazor Server:
(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');
const startReconnectionProcess = () => {
reconnectModal.style.display = 'block';
let isCanceled = false;
(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;
await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));
if (isCanceled) {
return;
}
try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected; reload the page.
location.reload();
return;
}
// Successfully reconnected to the server.
return;
} catch {
// Didn't reach the server; try again.
}
}
// Retried too many times; reload the page.
location.reload();
})();
return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};
let currentReconnectionProcess = null;
Blazor.start({
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
});
})();
有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor 启动。
调整服务器端重新连接重试计数和间隔
若要调整重新连接重试次数和间隔,请设置重试次数 (maxRetries
) 和允许每次重试运行的毫秒数 (retryIntervalMilliseconds
)。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
}
});
</script>
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
});
</script>
在前面的示例中,{BLAZOR SCRIPT}
占位符是 Blazor 脚本路径和文件名。 有关该脚本的位置和要使用的路径,请参阅 ASP.NET Core Blazor 项目结构。
当用户导航回线路已断开连接的应用时,将立即尝试重新连接,而不是等待下一次重新连接间隔的持续时间。 此行为旨在尽快恢复用户的连接。
默认重新连接计时使用计算的退避策略。 在开始时进行数次快速连续的重新连接尝试,而后在每次尝试后引入计算的延迟。 计算重试间隔的默认逻辑是一个实现细节,如有更改,恕不另行通知,但你可以在 computeDefaultRetryInterval
函数(引用源)中找到 Blazor 框架使用的默认逻辑。
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
通过指定函数来计算重试间隔可以自定义重试间隔行为。 在以下指数退避示例中,重试间隔是通过将先前重新连接的尝试次数乘以 1,000 毫秒计算得出的。 当先前尝试重新连接的计数 (previousAttempts
) 大于最大重试限制 (maxRetries
) 时,会将 null
分配给重试间隔 (retryIntervalMilliseconds
) 以停止进一步的重新连接尝试:
Blazor.start({
circuit: {
reconnectionOptions: {
retryIntervalMilliseconds: (previousAttempts, maxRetries) =>
previousAttempts >= maxRetries ? null : previousAttempts * 1000
},
},
});
另一种方法是指定重试间隔的确切顺序。 在最后一次指定的重试间隔后,重试会停止,因为 retryIntervalMilliseconds
函数返回 undefined
:
Blazor.start({
circuit: {
reconnectionOptions: {
retryIntervalMilliseconds:
Array.prototype.at.bind([0, 1000, 2000, 5000, 10000, 15000, 30000]),
},
},
});
有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor 启动。
控制重新连接 UI 何时出现
在以下情况下,控制重新连接 UI 何时出现可能会非常有用:
- 由于内部网络或 Internet 延迟导致的 ping 超时,所部署的应用经常显示重新连接 UI,并且你希望增加延迟。
- 应用应及早向用户通知连接已断开,并且你希望缩短延迟时间。
重新连接 UI 的出现时间受调整客户端上的保持连接间隔和超时的影响。 当客户端达到服务器超时时,会出现重新连接 UI(withServerTimeout
,客户端配置部分)。 但是,更改 withServerTimeout
的值需要更改以下指南中描述的其他保持连接、超时和握手设置。
作为以下指导的一般建议:
- 保持连接间隔应在客户端和服务器配置之间相匹配。
- 超时应至少是分配给始终连接间隔的值的两倍。
服务器配置
指定以下设置:
- ClientTimeoutInterval(默认值:30 秒):在服务器关闭连接之前,客户端必须发送消息的时间窗口。
- HandshakeTimeout(默认值:15 秒):服务器用于超时客户端传入握手请求的间隔。
- KeepAliveInterval(默认值:15 秒):服务器用来向已连接的客户端发送保持连接 ping 的间隔。 请注意,客户端上还存在一个“保持连接”间隔设置,它应与服务器的值匹配。
可以增加 ClientTimeoutInterval 和 HandshakeTimeout,而 KeepAliveInterval 则可以保持不变。 重要的考虑事项是,如果更改该值,请确保超时至少是保持连接间隔的值的两倍,并且保持连接间隔在服务器和客户端之间匹配。 有关详细信息,请参阅在客户端部分中配置 SignalR 超时和保持活动状态。
在如下示例中:
- 将 ClientTimeoutInterval 增加到 60 秒(默认值:30 秒)。
- 将 HandshakeTimeout 增加到 30 秒(默认值:15 秒)。
- KeepAliveInterval 不在开发人员代码中设置,并且使用其默认值 15 秒。 减少保持连接间隔的值会增加通信 ping 的频率,这会增加应用、服务器和网络的负载。 在降低保持连接间隔时,必须注意避免产生性能不佳的情况。
服务器项目的 Program
文件中的 Blazor Web App(.NET 8 或更高版本):
builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
Program
文件中的 Blazor Server:
builder.Services.AddServerSideBlazor()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
有关详细信息,请参阅服务器端线路处理程序选项部分。
客户端配置
设置以下内容:
withServerTimeout
(默认值:30 秒):配置线路中心连接的服务器超时(以毫秒为单位)。withKeepAliveInterval
(默认值:15 秒):连接发送 Keep-Alive 消息的间隔(以毫秒为单位)。
可以增加服务器超时,而保持连接间隔则可以保持不变。 重要的考虑事项是,如果更改该值,请确保服务器超时至少是保持连接间隔的值的两倍,并且保持连接间隔值在服务器和客户端之间匹配。 有关详细信息,请参阅在客户端部分中配置 SignalR 超时和保持活动状态。
在以下启动配置示例(Blazor 脚本的位置)中,服务器超时使用了 60 秒自定义值。 未设置 Keep-Alive 间隔(withKeepAliveInterval
),使用其默认值 15 秒。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(60000);
}
}
});
</script>
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(60000);
}
});
</script>
在组件中创建中心连接时,在 WithServerTimeout 上设置服务器超时(HubConnectionBuilder,默认值:30 秒)。 在生成 HubConnection 上设置 HandshakeTimeout(默认值:15 秒)。 确认超时至少是保持连接间隔(WithKeepAliveInterval/KeepAliveInterval)的两倍,并且保持连接值在服务器和客户端之间匹配。
以下示例基于SignalR与Blazor教程中的Index
组件。 将服务器超时增加到 60 秒,并将握手超时增加到 30 秒。 未设置 Keep-Alive 间隔,使用其默认值 15 秒。
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(60))
.Build();
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
设置以下内容:
serverTimeoutInMilliseconds
(默认值:30 秒):配置线路中心连接的服务器超时(以毫秒为单位)。-
keepAliveIntervalInMilliseconds
(默认值:15 秒):连接发送保持连接消息的间隔(以毫秒为单位)。
可以增加服务器超时,而保持连接间隔则可以保持不变。 重要的考虑事项是,如果更改数值,请确保服务器超时时间至少是Keep-Alive间隔的两倍,并且服务器和客户端之间的Keep-Alive间隔值保持一致。 有关详细信息,请参阅在客户端部分中配置 SignalR 超时和保持活动状态。
在以下启动配置示例(Blazor 脚本的位置)中,服务器超时使用了 60 秒自定义值。 未设置 Keep-Alive 间隔(keepAliveIntervalInMilliseconds
),使用其默认值 15 秒。
在 Pages/_Host.cshtml
中:
<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 60000;
builder.build = () => {
return c;
};
}
});
</script>
在组件中创建中心连接时,在生成的 ServerTimeout 上设置 HandshakeTimeout(默认值:30 秒)和 HubConnection(默认值:15 秒)。 确认超时至少是保持连接间隔的两倍。 确认服务器与客户端的 Keep-Alive 间隔是否一致。
以下示例基于 SignalR 与 Blazor 结合使用教程中的 Index
组件。 将 ServerTimeout 增加到 60 秒,并将 HandshakeTimeout 增加到 30 秒。 未设置 Keep-Alive 间隔(KeepAliveInterval),使用其默认值 15 秒。
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
断开 Blazor 的 SignalR 线路与客户端的连接
触发 unload
页面事件时,Blazor 的 SignalR 线路断开。 若要断开客户端上其他方案的线路连接,请在相应的事件处理程序中调用 Blazor.disconnect
。 在下面的示例中,当页面隐藏(pagehide
事件)时,线路会断开连接:
window.addEventListener('pagehide', () => {
Blazor.disconnect();
});
有关 Blazor 启动的详细信息,请参阅 ASP.NET Core Blazor 启动。
服务器端线路处理程序
可以定义线路处理程序,它允许在用户线路的状态发生更改时运行代码。 线路处理程序通过从 CircuitHandler 派生并在应用的服务容器中注册该类实现。 以下线路处理程序示例跟踪打开的 SignalR 连接。
TrackingCircuitHandler.cs
:
using Microsoft.AspNetCore.Components.Server.Circuits;
public class TrackingCircuitHandler : CircuitHandler
{
private HashSet<Circuit> circuits = new();
public override Task OnConnectionUpAsync(Circuit circuit,
CancellationToken cancellationToken)
{
circuits.Add(circuit);
return Task.CompletedTask;
}
public override Task OnConnectionDownAsync(Circuit circuit,
CancellationToken cancellationToken)
{
circuits.Remove(circuit);
return Task.CompletedTask;
}
public int ConnectedCircuits => circuits.Count;
}
线路处理程序使用 DI 注册。 每个线路实例都会创建区分范围的实例。 借助前面示例中的 TrackingCircuitHandler
创建单例服务,因为必须跟踪所有电路的状态。
在 Program
文件中:
builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
在 Startup.cs
的 Startup.ConfigureServices
中:
services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
如果自定义线路处理程序的方法引发未处理异常,则该异常会导致 线路产生严重错误。 若要容忍处理程序代码或被调用方法中的异常,请使用错误处理和日志记录将代码包装到一个或多个 try-catch
语句中。
当会话因用户断开连接而结束且框架正在清理会话状态时,框架会处置会话的 DI 作用域。 处置范围时会处置所有实现 System.IDisposable 的区分线路范围的 DI 服务。 如果有任何 DI 服务在处置期间引发未处理异常,则框架会记录该异常。 有关详细信息,请参阅 ASP.NET Core Blazor 依赖项注入。
用于为自定义服务捕获用户的服务器端线路处理程序
使用 CircuitHandler 从 AuthenticationStateProvider 捕获用户,并在服务中设置此用户。 有关详细信息和示例代码,请参阅 ASP.NET 核心服务器端和其他 Blazor Web App 安全方案。
当没有剩余的交互式服务器组件时,关闭线路
交互式服务器组件使用与浏览器的实时连接(称为线路)来处理 Web UI 事件。 在渲染根交互式服务器组件时,将创建电路及其关联状态。 当页面上没有剩余的交互式服务器组件时,线路将关闭,以释放服务器资源。
在不同的 URL 上启动 SignalR 电路
通过将 autostart="false"
添加到 Blazor<script>
标记(启动脚本的位置 Blazor),防止应用程序自动启动。 使用 Blazor.start
手动建立线路 URL。 以下示例使用路径 /signalr
。
Blazor Web App:
- <script src="_framework/blazor.web.js"></script>
+ <script src="_framework/blazor.web.js" autostart="false"></script>
+ <script>
+ Blazor.start({
+ circuit: {
+ configureSignalR: builder => builder.withUrl("/signalr")
+ },
+ });
+ </script>
Blazor Server:
- <script src="_framework/blazor.server.js"></script>
+ <script src="_framework/blazor.server.js" autostart="false"></script>
+ <script>
+ Blazor.start({
+ configureSignalR: builder => builder.withUrl("/signalr")
+ });
+ </script>
将以下 MapBlazorHub 调用与中心路径一起添加到服务器应用的 Program
文件中的中间件处理管道。
Blazor Web App:
app.MapBlazorHub("/signalr");
Blazor Server:
保留文件中对 MapBlazorHub 的现有调用,并添加一个新的调用 MapBlazorHub,路径为:
app.MapBlazorHub();
+ app.MapBlazorHub("/signalr");
Windows 身份验证的模拟
使用 HubConnection 创建经过身份验证的中心连接(UseDefaultCredentials),以指示对 HTTP 请求使用默认凭据。 有关详细信息,请参阅 ASP.NET Core SignalR中的身份验证和授权。
当应用作为 Windows 身份验证下的登录用户(可能是用户的个人或工作帐户)在 IIS Express 中运行时,默认凭据是已登录用户的凭据。
将应用发布到 IIS 时,应用在 应用程序池 Identity下运行。 HubConnection 以托管应用的 IIS“用户”帐户连接,而不是以访问页面的用户连接。
使用 HubConnection 实现模拟,以使用浏览用户的身份。
在以下示例中:
- 来自身份验证状态提供程序的用户被强制转换为 WindowsIdentity。
- 标识的访问令牌与生成和启动 HubConnection 的代码一起传递给 WindowsIdentity.RunImpersonatedAsync。
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState?.User.Identity is not null)
{
var user = authState.User.Identity as WindowsIdentity;
if (user is not null)
{
await WindowsIdentity.RunImpersonatedAsync(user.AccessToken,
async () =>
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavManager.ToAbsoluteUri("/hub"), config =>
{
config.UseDefaultCredentials = true;
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string>("name", userName =>
{
name = userName;
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
});
}
}
}
在前面的代码中,NavManager
是 NavigationManager,AuthenticationStateProvider
是 AuthenticationStateProvider 服务实例(AuthenticationStateProvider
文档)。