结合使用 ASP.NET Core SignalR 和 Blazor

本教程介绍结合使用 SignalR 和 Blazor 生成实时应用的基础知识。 您将学习如何:

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

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

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

先决条件

创建托管 Blazor WebAssembly 应用

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

备注

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

备注

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

  1. 创建新项目。

  2. 选择“Blazor 应用”,然后选择“下一步”。

  3. 在“项目名称”字段中键入 BlazorWebAssemblySignalRApp。 确认“位置”条目正确无误或为项目提供位置。 选择“创建”。

  4. 选择“Blazor WebAssembly 应用”模板。

  5. 在“高级”下选中“托管的 ASP.NET Core”复选框 。

  6. 选择“创建”。

添加 SignalR 客户端库

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

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

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

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

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

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

添加 SignalR 集线器

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

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

  1. BlazorWebAssemblySignalRApp.Server 项目中打开 Startup.cs 文件。

  2. ChatHub 类的命名空间添加到文件顶部:

    using BlazorWebAssemblySignalRApp.Server.Hubs;
    
  1. 将 SignalR 和响应压缩中间件服务添加到 Startup.ConfigureServices

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSignalR();
        services.AddControllersWithViews();
        services.AddRazorPages();
        services.AddResponseCompression(opts =>
        {
            opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                new[] { "application/octet-stream" });
        });
    }
    
  2. Startup.Configure中:

    • 使用处理管道的配置顶部的“响应压缩中间件”。
    • 在控制器终结点和客户端回退之间,为中心添加一个终结点。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseResponseCompression();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseWebAssemblyDebugging();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseBlazorFrameworkFiles();
        app.UseStaticFiles();
    
        app.UseRouting();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
            endpoints.MapControllers();
            endpoints.MapHub<ChatHub>("/chathub");
            endpoints.MapFallbackToFile("index.html");
        });
    }
    
  1. 将 SignalR 和响应压缩中间件服务添加到 Startup.ConfigureServices

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSignalR();
        services.AddControllersWithViews();
        services.AddResponseCompression(opts =>
        {
            opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                new[] { "application/octet-stream" });
        });
    }
    
  2. Startup.Configure中:

    • 使用处理管道的配置顶部的“响应压缩中间件”。
    • 在控制器终结点和客户端回退之间,为中心添加一个终结点。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseResponseCompression();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseWebAssemblyDebugging();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseBlazorFrameworkFiles();
        app.UseStaticFiles();
    
        app.UseRouting();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapHub<ChatHub>("/chathub");
            endpoints.MapFallbackToFile("index.html");
        });
    }
    

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

  1. BlazorWebAssemblySignalRApp.Client 项目中打开 Pages/Index.razor 文件。
  1. 将标记替换为以下代码:

    @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()
        {
            await hubConnection.DisposeAsync();
        }
    }
    
  1. 将标记替换为以下代码:

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

运行应用

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

  1. 在“解决方案资源管理器”中,选择 BlazorWebAssemblySignalRApp.Server 项目。 按 F5 来运行应用并进行调试,或者按 Ctrl+F5 来运行应用但不调试。

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

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

    SignalR Blazor 示例应用在两个浏览器窗口中打开,显示交换的消息。

    Quotes:Star Trek VI:The Undiscovered Country ©1991 Paramount

创建 Blazor Server 应用

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

备注

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

备注

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

  1. 创建新项目。

  2. 选择“Blazor 应用”,然后选择“下一步”。

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

  4. 选择“Blazor Server 应用”模板。

  5. 选择“创建”。

添加 SignalR 客户端库

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

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

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

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

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

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

添加 System.Text.Encodings.Web 包

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

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

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

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

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

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

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

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

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

添加 SignalR 集线器

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

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

  1. 打开 Startup.cs 文件。

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

    using Microsoft.AspNetCore.ResponseCompression;
    using BlazorServerSignalRApp.Server.Hubs;
    
  1. 将响应压缩中间件服务添加到 Startup.ConfigureServices

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddSingleton<WeatherForecastService>();
        services.AddResponseCompression(opts =>
        {
            opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                new[] { "application/octet-stream" });
        });
    }
    
  2. Startup.Configure中:

    • 使用处理管道的配置顶部的“响应压缩中间件”。
    • 在映射 Blazor 中心的终结点和客户端回退之间,为中心添加一个终结点。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseResponseCompression();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseStaticFiles();
    
        app.UseRouting();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapHub<ChatHub>("/chathub");
            endpoints.MapFallbackToPage("/_Host");
        });
    }
    
  1. 将响应压缩中间件服务添加到 Startup.ConfigureServices

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddSingleton<WeatherForecastService>();
        services.AddResponseCompression(opts =>
        {
            opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                new[] { "application/octet-stream" });
        });
    }
    
  2. Startup.Configure中:

    • 使用处理管道的配置顶部的“响应压缩中间件”。
    • 在映射 Blazor 中心的终结点和客户端回退之间,为中心添加一个终结点。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseResponseCompression();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseStaticFiles();
    
        app.UseRouting();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapHub<ChatHub>("/chathub");
            endpoints.MapFallbackToPage("/_Host");
        });
    }
    

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

  1. 打开 Pages/Index.razor 文件。
  1. 将标记替换为以下代码:

    @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()
        {
            await hubConnection.DisposeAsync();
        }
    }
    
  1. 将标记替换为以下代码:

    @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()
        {
            await hubConnection.DisposeAsync();
        }
    }
    

运行应用

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

  1. F5 来运行应用并进行调试,或者按 Ctrl+F5 来运行应用但不调试。

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

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

    SignalR Blazor 示例应用在两个浏览器窗口中打开,显示交换的消息。

    Quotes:Star Trek VI:The Undiscovered Country ©1991 Paramount

后续步骤

在本教程中,你了解了如何执行以下操作:

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

若要了解有关生成 Blazor 应用的详细信息,请参阅 Blazor 文档:

其他资源