结合使用 ASP.NET Core SignalR 和 Blazor

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 ASP.NET Core 8.0 版本

本教程提供了使用 SignalR 和 Blazor 生成实时应用的基本工作经验。 本文适用于已经熟悉 SignalR 并正在寻求了解如何在 SignalR 应用中使用 Blazor 的开发人员。 有关 SignalR 和 Blazor 框架的详细指南,请参阅以下参考文档集和 API 文档:

了解如何:

  • 创建 Blazor 应用
  • 添加 SignalR 客户端库
  • 添加 SignalR 集线器
  • 添加 SignalR 服务和 SignalR 中心的终结点
  • 添加用于聊天的 Razor 组件代码

在本教程结束时,你将拥有一个正常运行的聊天应用。

先决条件

具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2022 或更高版本

示例应用

本教程不需要下载教程的示例聊天应用。 示例应用是按照本教程的步骤生成的最终工作应用。

查看或下载示例代码

创建 Blazor Web 应用

按照所选工具的指南进行操作:

注意

需要 Visual Studio 2022 或更高版本以及 .NET Core SDK 8.0.0 或更高版本。

创建新项目。

选择“Blazor Web 应用”模板。 选择“下一步”。

在“项目名称”字段中键入 BlazorSignalRApp。 确认“位置”条目正确无误或为项目提供位置。 选择下一步

确认 Framework 是 .NET 8 或更高版本。 选择“创建”。

添加 SignalR 客户端库

在“解决方案资源管理器”中,右键单击 BlazorSignalRApp 项目,然后选择“管理 NuGet 包” 。

在“管理 NuGet 包”对话框中,确认“包源”设置为“nuget.org” 。

选择“浏览”后,在搜索框中键入“Microsoft.AspNetCore.SignalR.Client”。

在搜索结果中,选择 Microsoft.AspNetCore.SignalR.Client 包的最新版本。 选择“安装” 。

如果出现“预览更改”对话框,则选择“确定”。

如果出现“许可证接受”对话框,如果你同意许可条款,请选择“我接受”。

添加 SignalR 集线器

创建 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 中心添加服务和终结点

打开 Program 文件。

Microsoft.AspNetCore.ResponseCompressionChatHub 类的命名空间添加到文件的顶部:

using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;

添加响应压缩中间件服务:

builder.Services.AddResponseCompression(opts =>
{
   opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
         new[] { "application/octet-stream" });
});

在处理管道的配置顶部使用响应压缩中间件:

app.UseResponseCompression();

紧接在映射 Razor 组件 (app.MapRazorComponents<T>()) 的行后面,为行添加一个终结点:

app.MapHub<ChatHub>("/chathub");

添加用于聊天的 Razor 组件代码

打开 Components/Pages/Home.razor 文件。

将标记替换为以下代码:

@page "/"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Home</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);
            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();
        }
    }
}

注意

使用热重载时,请在 Development 环境中禁用响应压缩中间件。 有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南

运行应用

按照工具的指南进行操作:

F5 来运行应用并进行调试,或者按 Ctrl+F5 (Windows)/+F5 (macOS) 来运行应用但不调试。

从地址栏复制 URL,打开另一个浏览器实例或选项卡,并在地址栏中粘贴该 URL。

选择任一浏览器,输入名称和消息,然后选择按钮发送消息。 两个页面上立即显示名称和消息:

SignalRBlazor sample app open in two browser windows showing exchanged messages.

引文: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。

选择任一浏览器,输入名称和消息,然后选择按钮发送消息。 两个页面上立即显示名称和消息:

SignalRBlazor sample app open in two browser windows showing exchanged messages.

引文:Star Trek VI: The Undiscovered Country ©1991 Paramount

Blazor Server 体验

创建应用

按照所选工具的指南创建 Blazor Server 应用:

注意

需要 Visual Studio 2022 或更高版本以及 .NET Core SDK 6.0.0 或更高版本。

创建新项目。

选择“Blazor Server 应用”模板。 选择“下一步”。

在“项目名称”字段中键入 BlazorServerSignalRApp。 确认“位置”条目正确无误或为项目提供位置。 选择“下一步”。

选择“创建”。

添加 SignalR 客户端库

在“解决方案资源管理器”中,右键单击 BlazorServerSignalRApp 项目,然后选择“管理 NuGet 包” 。

在“管理 NuGet 包”对话框中,确认“包源”设置为“nuget.org” 。

选择“浏览”后,在搜索框中键入“Microsoft.AspNetCore.SignalR.Client”。

在搜索结果中,选择 Microsoft.AspNetCore.SignalR.Client 包。 将版本设置为与应用共享框架匹配的版本。 选择“安装” 。

如果出现“预览更改”对话框,则选择“确定”。

如果出现“许可证接受”对话框,如果你同意许可条款,请选择“我接受”。

添加 System.Text.Encodings.Web 包

本部分仅适用于 ASP.NET Core 版本 3.x 的应用。

由于在 ASP.NET Core 3.x 应用中使用 System.Text.Json 5.x 时出现包解析问题,项目需要 System.Text.Encodings.Web 的包引用。 基础问题已在修补程序版本中解决并向后移植到 ASP.NET Core 5.0。 有关详细信息,请参阅 System.Text.Json 定义无依赖项的 netcoreapp3.0(dotnet/运行时 #45560)

若要将 System.Text.Encodings.Web 添加到项目,请按照所选工具的指南进行操作:

在“解决方案资源管理器”中,右键单击 BlazorServerSignalRApp 项目,然后选择“管理 NuGet 包” 。

在“管理 NuGet 包”对话框中,确认“包源”设置为“nuget.org” 。

选择“浏览”后,在搜索框中键入“System.Text.Encodings.Web”。

在搜索结果中,选择 System.Text.Encodings.Web 包。 选择与正在使用的共享框架相匹配的包版本。 选择“安装” 。

如果出现“预览更改”对话框,则选择“确定”。

如果出现“许可证接受”对话框,如果你同意许可条款,请选择“我接受”。

添加 SignalR 集线器

创建 Hubs(复数)文件夹,并添加以下 ChatHub 类 (Hubs/ChatHub.cs):

using Microsoft.AspNetCore.SignalR;

namespace BlazorServerSignalRApp.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 BlazorServerSignalRApp.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 BlazorServerSignalRApp.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 BlazorServerSignalRApp.Server.Hubs
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
}

为 SignalR 中心添加服务和终结点

打开 Program.cs 文件。

Microsoft.AspNetCore.ResponseCompressionChatHub 类的命名空间添加到文件的顶部:

using Microsoft.AspNetCore.ResponseCompression;
using BlazorServerSignalRApp.Server.Hubs;

添加响应压缩中间件服务:

builder.Services.AddResponseCompression(opts =>
{
   opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
         new[] { "application/octet-stream" });
});

在处理管道的配置顶部使用响应压缩中间件:

app.UseResponseCompression();

在映射 Blazor 中心的终结点和客户端回退之间,紧接在 app.MapBlazorHub(); 行之后为中心添加一个终结点:

app.MapHub<ChatHub>("/chathub");

打开 Startup.cs 文件。

Microsoft.AspNetCore.ResponseCompressionChatHub 类的命名空间添加到文件的顶部:

using Microsoft.AspNetCore.ResponseCompression;
using BlazorServerSignalRApp.Server.Hubs;

添加响应压缩中间件服务:

services.AddResponseCompression(opts =>
{
   opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
         new[] { "application/octet-stream" });
});

在处理管道的配置顶部使用响应压缩中间件:

app.UseResponseCompression();

在映射 Blazor 中心的终结点和客户端回退之间,紧接在 endpoints.MapBlazorHub(); 行之后为中心添加一个终结点:

endpoints.MapHub<ChatHub>("/chathub");

添加用于聊天的 Razor 组件代码

打开 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);
            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();
        }
    }
}
@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);
            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();
        }
    }
}
@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);
            InvokeAsync(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 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);
            InvokeAsync(StateHasChanged);
        });

        await hubConnection.StartAsync();
    }

    async Task Send() =>
        await hubConnection.SendAsync("SendMessage", userInput, messageInput);

    public bool IsConnected =>
        hubConnection.State == HubConnectionState.Connected;

    public async ValueTask DisposeAsync()
    {
        await hubConnection?.DisposeAsync();
    }
}

注意

使用热重载时,请在 Development 环境中禁用响应压缩中间件。 有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南

运行应用

按照工具的指南进行操作:

F5 来运行应用并进行调试,或者按 Ctrl+F5 (Windows)/+F5 (macOS) 来运行应用但不调试。

从地址栏复制 URL,打开另一个浏览器实例或选项卡,并在地址栏中粘贴该 URL。

选择任一浏览器,输入名称和消息,然后选择按钮发送消息。 两个页面上立即显示名称和消息:

SignalRBlazor sample app open in two browser windows showing exchanged messages.

引文:Star Trek VI: The Undiscovered Country ©1991 Paramount

后续步骤

在本教程中,你将了解:

  • 创建 Blazor 应用
  • 添加 SignalR 客户端库
  • 添加 SignalR 集线器
  • 添加 SignalR 服务和 SignalR 中心的终结点
  • 添加用于聊天的 Razor 组件代码

有关 SignalR 和 Blazor 框架的详细指南,请参阅以下参考文档集:

其他资源