结合使用 ASP.NET Core SignalR 和 Blazor
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
本教程提供了使用 SignalR 和 Blazor 生成实时应用的基本工作经验。 本文适用于已经熟悉 SignalR 并正在寻求了解如何在 SignalR 应用中使用 Blazor 的开发人员。 有关 SignalR 和 Blazor 框架的详细指南,请参阅以下参考文档集和 API 文档:
了解如何:
- 创建 Blazor 应用
- 添加 SignalR 客户端库
- 添加 SignalR 集线器
- 添加 SignalR 服务和 SignalR 中心的终结点
- 添加用于聊天的 Razor 组件代码
在本教程结束时,你将拥有一个正常运行的聊天应用。
先决条件
Visual Studio(最新预览版)与 ASP.NET 和 Web 开发工作负载
示例应用
本教程不需要下载教程的示例聊天应用。 示例应用是按照本教程的步骤生成的最终工作应用。 打开示例存储库时,打开计划面向的版本文件夹,找到名为 BlazorSignalRApp
的示例。
本教程不需要下载教程的示例聊天应用。 示例应用是按照本教程的步骤生成的最终工作应用。 打开示例存储库时,打开计划面向的版本文件夹,找到名为 BlazorWebAssemblySignalRApp
的示例。
创建 Blazor Web App
按照所选工具的指南进行操作:
注意
需要 Visual Studio 2022 或更高版本以及 .NET Core SDK 8.0.0 或更高版本。
在 Visual Studio 中:
- 从启动窗口选择“创建新项目”,或者从菜单栏选择“文件”>“新建”>“项目”。
- 在“创建新项目”对话框中,从项目模板列表中选择“Blazor Web App”。 选择“下一步”按钮。
- 在“配置新项目”对话框中,在“项目名称”字段中将项目命名为
BlazorSignalRApp
(包括匹配大写)。 使用这个确切的项目名称非常重要,可确保命名空间与从教程复制到要构建的应用中的代码匹配。 - 确认应用的位置是否合适。 将“将解决方案和项目放在同一目录中”复选框保持在选中状态。 选择“下一步”按钮。
- 在“其他信息”对话框中,使用以下设置:
- 框架:确认选择了最新框架。 如果 Visual Studio 的“框架”下拉列表不包含最新的可用 .NET Framework,请更新 Visual Studio 并重启教程。
- 身份验证类型:无
- 配置 HTTPS:选中
- 交互式呈现模式:WebAssembly
- 交互位置:每页/每组件
- 包括示例页面:选中
- 不使用顶级语句未选中
- 选择创建。
本文中的指南针对 SignalR 客户端使用的是 WebAssembly 组件,因为使用 SignalR 从同一应用中的交互式服务器组件连接到中心没有意义,因为这可能会导致服务器端口耗尽。
添加 SignalR 客户端库
在“解决方案资源管理器”中,右键单击 BlazorSignalRApp.Client
项目,然后选择“管理 NuGet 包” 。
在“管理 NuGet 包”对话框中,确认“包源”设置为“nuget.org
” 。
选择“浏览”后,在搜索框中键入“Microsoft.AspNetCore.SignalR.Client
”。
在搜索结果中,选择 Microsoft.AspNetCore.SignalR.Client
包的最新版本。 选择“安装” 。
如果出现“预览更改”对话框,则选择“确定”。
如果出现“许可证接受”对话框,如果你同意许可条款,请选择“我接受”。
添加 SignalR 集线器
在服务器 BlazorSignalRApp
项目中,创建 Hubs
(复数)文件夹,并添加以下 ChatHub
类 (Hubs/ChatHub.cs
):
using Microsoft.AspNetCore.SignalR;
namespace BlazorSignalRApp.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
为 SignalR 中心添加服务和终结点
打开服务器 BlazorSignalRApp
项目的 Program
文件。
将 Microsoft.AspNetCore.ResponseCompression 和 ChatHub
类的命名空间添加到文件的顶部:
using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;
添加 SignalR 和响应压缩中间件服务:
builder.Services.AddSignalR();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
["application/octet-stream"]);
});
使用处理管道的配置顶部的“响应压缩中间件”。 使以下代码行紧跟在构建应用的行 (var app = builder.Build();
) 后面:
app.UseResponseCompression();
紧接在运行应用的行 (app.Run();
) 前面,为中心添加一个终结点:
app.MapHub<ChatHub>("/chathub");
添加用于聊天的 Razor 组件代码
将以下 Pages/Chat.razor
文件添加到 BlazorSignalRApp.Client
项目:
@page "/chat"
@rendermode InteractiveWebAssembly
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Chat</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = [];
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
将一个条目添加到 NavMenu
组件以访问聊天页面。 在紧跟在 Weather
组件的 <div>
块后面的 Components/Layout/NavMenu.razor
中,添加以下 <div>
块:
<div class="nav-item px-3">
<NavLink class="nav-link" href="chat">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Chat
</NavLink>
</div>
注意
使用热重载时,请在 Development
环境中禁用响应压缩中间件。 有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南。
运行应用
按照工具的指南进行操作:
在解决方案资源管理器中选择服务器 BlazorSignalRApp
项目后,按 F5 来运行应用并进行调试,或者按 Ctrl+F5 (Windows)/⌘+F5 (macOS) 来运行应用但不调试。
从地址栏复制 URL,打开另一个浏览器实例或选项卡,并在地址栏中粘贴该 URL。
选择任一浏览器,输入名称和消息,然后选择按钮发送消息。 两个页面上立即显示名称和消息:
引文:Star Trek VI: The Undiscovered Country ©1991 Paramount
托管 Blazor WebAssembly 体验
创建应用
按照所选工具的指南创建托管 Blazor WebAssembly 应用:
注意
需要 Visual Studio 2022 或更高版本以及 .NET Core SDK 6.0.0 或更高版本。
创建新项目。
选择“Blazor WebAssembly 应用”模板。 选择“下一步”。
在“项目名称”字段中键入 BlazorWebAssemblySignalRApp
。 确认“位置”条目正确无误或为项目提供位置。 选择“下一步”。
在“其他信息”对话框中,选中“ASP.NET Core 托管”复选框。
选择“创建”。
确认已创建托管 Blazor WebAssembly 应用:在解决方案资源管理器中,确认是否存在 Client 项目和 Server 项目。 如果两个项目不存在,请重新开始并在选择“创建”之前确认已选中“ASP.NET Core 托管”复选框。
添加 SignalR 客户端库
在“解决方案资源管理器”中,右键单击 BlazorWebAssemblySignalRApp.Client
项目,然后选择“管理 NuGet 包” 。
在“管理 NuGet 包”对话框中,确认“包源”设置为“nuget.org
” 。
选择“浏览”后,在搜索框中键入“Microsoft.AspNetCore.SignalR.Client
”。
在搜索结果中,选择 Microsoft.AspNetCore.SignalR.Client
包。 将版本设置为与应用共享框架匹配的版本。 选择“安装” 。
如果出现“预览更改”对话框,则选择“确定”。
如果出现“许可证接受”对话框,如果你同意许可条款,请选择“我接受”。
添加 SignalR 集线器
在 BlazorWebAssemblySignalRApp.Server
项目中,创建 Hubs
(复数)文件夹,并添加以下 ChatHub
类 (Hubs/ChatHub.cs
):
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace BlazorWebAssemblySignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
为 SignalR 中心添加服务和终结点
在 BlazorWebAssemblySignalRApp.Server
项目中打开 Program.cs
文件。
将 ChatHub
类的命名空间添加到文件顶部:
using BlazorWebAssemblySignalRApp.Server.Hubs;
添加 SignalR 和响应压缩中间件服务:
builder.Services.AddSignalR();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
在处理管道的配置顶部,紧接在生成应用的行之后使用响应压缩中间件:
app.UseResponseCompression();
在控制器终结点和客户端回退之间,为中心添加一个终结点。 紧接行 app.MapControllers();
之后,添加以下行:
app.MapHub<ChatHub>("/chathub");
在 BlazorWebAssemblySignalRApp.Server
项目中打开 Startup.cs
文件。
将 ChatHub
类的命名空间添加到文件顶部:
using BlazorWebAssemblySignalRApp.Server.Hubs;
添加 SignalR 和响应压缩中间件服务:
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
在处理管道的配置顶部使用响应压缩中间件:
app.UseResponseCompression();
在控制器的终结点和客户端回退之间,紧接在 endpoints.MapControllers();
行之后为中心添加一个终结点:
endpoints.MapHub<ChatHub>("/chathub");
添加用于聊天的 Razor 组件代码
在 BlazorWebAssemblySignalRApp.Client
项目中打开 Pages/Index.razor
文件。
将标记替换为以下代码:
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Index</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Index</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection hubConnection;
private List<string> messages = new List<string>();
private string userInput;
private string messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
async Task Send() =>
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@implements IDisposable
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection hubConnection;
private List<string> messages = new List<string>();
private string userInput;
private string messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
StateHasChanged();
});
await hubConnection.StartAsync();
}
async Task Send() =>
await hubConnection.SendAsync("SendMessage", userInput, messageInput);
public bool IsConnected =>
hubConnection.State == HubConnectionState.Connected;
public void Dispose()
{
_ = hubConnection?.DisposeAsync();
}
}
注意
使用热重载时,请在 Development
环境中禁用响应压缩中间件。 有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南。
运行应用
按照工具的指南进行操作:
在“解决方案资源管理器”中,选择 BlazorWebAssemblySignalRApp.Server
项目。 按 F5 来运行应用并进行调试,或者按 Ctrl+F5 (Windows)/⌘+F5 (macOS) 来运行应用但不调试。
重要
执行托管应用 Blazor WebAssembly 时,请从解决方案的 Server 项目运行应用。
Google Chrome 或 Microsoft Edge 必须是调试会话的选定浏览器。
如果应用无法在浏览器中启动:
- 在 .NET 控制台中,确认解决方案是否从“Server”项目运行。
- 使用浏览器的重新加载按钮刷新浏览器。
从地址栏复制 URL,打开另一个浏览器实例或选项卡,并在地址栏中粘贴该 URL。
选择任一浏览器,输入名称和消息,然后选择按钮发送消息。 两个页面上立即显示名称和消息:
引文:Star Trek VI: The Undiscovered Country ©1991 Paramount
后续步骤
在本教程中,你将了解:
- 创建 Blazor 应用
- 添加 SignalR 客户端库
- 添加 SignalR 集线器
- 添加 SignalR 服务和 SignalR 中心的终结点
- 添加用于聊天的 Razor 组件代码
有关 SignalR 和 Blazor 框架的详细指南,请参阅以下参考文档集: