教程:构建 Blazor Server 聊天应用Tutorial: Build a Blazor Server chat app

本教程演示如何构建和修改 Blazor Server 应用。This tutorial shows you how to build and modify a Blazor Server app. 将了解如何执行以下操作:You'll learn how to:

  • 使用 Blazor Server 应用构建简单的聊天室。Build a simple chat room with Blazor Server app.
  • 修改 Razor 组件。Modify Razor components.
  • 在组件中使用事件处理和数据绑定。Use event handling and data binding in components.
  • 在 Visual Studio 中快速部署到 Azure 应用服务。Quick deploy to Azure App Service in Visual Studio.
  • 将本地 SignalR 迁移到 Azure SignalR 服务。Migrate local SignalR to Azure SignalR Service.

必备条件Prerequisites

Visual Studio 2019 预览版也可以正常使用,该版本与最新 Blazor Server 应用模板一起发布,后者面向更新的 .Net Core 版本。Visual Studio 2019 Preview version also works which is releasing with latest Blazor Server app template targeting newer .Net Core version.

在 Blazor Server 应用中构建本地聊天室Build a local chat room in Blazor Server app

从 Visual Studio 2019 16.2.0 版开始,Azure SignalR 服务是内置的 Web 应用发布进程,这样管理 Web 应用与 SignalR 服务之间的依赖项将更加方便。From Visual Studio 2019 version 16.2.0, Azure SignalR Service is build-in web app publish process, and manage dependencies between web app and SignalR service would be much more convenient. 无需更改任何代码,即可体验在开发本地环境中使用本地 SignalR,同时体验针对 Azure 应用服务使用 Azure SignalR 服务。You can experience working on local SignalR in dev local environment and working on Azure SignalR Service for Azure App Service at the same time without any code changes.

  1. 创建聊天 Blazor 应用Create a chat Blazor app

    在 Visual Studio 中,选择“新建项目”->“Blazor 应用”->(命名应用并选择一个文件夹)->“Blazor Server 应用”。In Visual Studio, choose Create a new project -> Blazor App -> (name the app and choose a folder) -> Blazor Server App. 确保已安装 .NET Core SDK 3.0+,使 Visual Studio 可正确识别目标框架。Make sure you've already installed .NET Core SDK 3.0+ to enable Visual Studio correctly recognize the target framework.

    blazor-chat-create blazor-chat-create

    或者,运行 cmdOr run cmd

    dotnet new blazorserver -o BlazorChat
    
  2. 添加 BlazorChatSampleHub.cs 文件以实现 Hub 进行聊天。Add a BlazorChatSampleHub.cs file to implement Hub for chat.

    using System;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.SignalR;
    
    namespace BlazorChat
    {
        public class BlazorChatSampleHub : Hub
        {
            public const string HubUrl = "/chat";
    
            public async Task Broadcast(string username, string message)
            {
                await Clients.All.SendAsync("Broadcast", username, message);
            }
    
            public override Task OnConnectedAsync()
            {
                Console.WriteLine($"{Context.ConnectionId} connected");
                return base.OnConnectedAsync();
            }
    
            public override async Task OnDisconnectedAsync(Exception e)
            {
                Console.WriteLine($"Disconnected {e?.Message} {Context.ConnectionId}");
                await base.OnDisconnectedAsync(e);
            }
        }
    }
    
  3. Startup.Configure() 中为中心添加一个终结点。Add an endpoint for the hub in Startup.Configure().

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
        endpoints.MapHub<BlazorChatSampleHub>(BlazorChatSampleHub.HubUrl);
    });
    
  4. 安装 Microsoft.AspNetCore.SignalR.Client 包以使用 SignalR 客户端。Install Microsoft.AspNetCore.SignalR.Client package to use SignalR client.

    dotnet add package Microsoft.AspNetCore.SignalR.Client --version 3.1.7
    
  5. Pages 文件夹下创建 ChartRoom.razor 以实现 SignalR 客户端。Create ChartRoom.razor under Pages folder to implement SignalR client. 按以下步骤操作,或直接复制 ChatRoom.razorFollow steps below or simply copy the ChatRoom.razor.

    1. 添加页面链接和引用。Add page link and reference.

      @page "/chatroom"
      @inject NavigationManager navigationManager
      @using Microsoft.AspNetCore.SignalR.Client;
      
    2. 将代码添加到新的 SignalR 客户端以发送和接收消息。Add code to new SignalR client to send and receive messages.

      @code {
          // flag to indicate chat status
          private bool _isChatting = false;
      
          // name of the user who will be chatting
          private string _username;
      
          // on-screen message
          private string _message;
      
          // new message input
          private string _newMessage;
      
          // list of messages in chat
          private List<Message> _messages = new List<Message>();
      
          private string _hubUrl;
          private HubConnection _hubConnection;
      
          public async Task Chat()
          {
              // check username is valid
              if (string.IsNullOrWhiteSpace(_username))
              {
                  _message = "Please enter a name";
                  return;
              };
      
              try
              {
                  // Start chatting and force refresh UI.
                  _isChatting = true;
                  await Task.Delay(1);
      
                  // remove old messages if any
                  _messages.Clear();
      
                  // Create the chat client
                  string baseUrl = navigationManager.BaseUri;
      
                  _hubUrl = baseUrl.TrimEnd('/') + BlazorChatSampleHub.HubUrl;
      
                  _hubConnection = new HubConnectionBuilder()
                      .WithUrl(_hubUrl)
                      .Build();
      
                  _hubConnection.On<string, string>("Broadcast", BroadcastMessage);
      
                  await _hubConnection.StartAsync();
      
                  await SendAsync($"[Notice] {_username} joined chat room.");
              }
              catch (Exception e)
              {
                  _message = $"ERROR: Failed to start chat client: {e.Message}";
                  _isChatting = false;
              }
          }
      
          private void BroadcastMessage(string name, string message)
          {
              bool isMine = name.Equals(_username, StringComparison.OrdinalIgnoreCase);
      
              _messages.Add(new Message(name, message, isMine));
      
              // Inform blazor the UI needs updating
              StateHasChanged();
          }
      
          private async Task DisconnectAsync()
          {
              if (_isChatting)
              {
                  await SendAsync($"[Notice] {_username} left chat room.");
      
                  await _hubConnection.StopAsync();
                  await _hubConnection.DisposeAsync();
      
                  _hubConnection = null;
                  _isChatting = false;
              }
          }
      
          private async Task SendAsync(string message)
          {
              if (_isChatting && !string.IsNullOrWhiteSpace(message))
              {
                  await _hubConnection.SendAsync("Broadcast", _username, message);
      
                  _newMessage = string.Empty;
              }
          }
      
          private class Message
          {
              public Message(string username, string body, bool mine)
              {
                  Username = username;
                  Body = body;
                  Mine = mine;
              }
      
              public string Username { get; set; }
              public string Body { get; set; }
              public bool Mine { get; set; }
      
              public bool IsNotice => Body.StartsWith("[Notice]");
      
              public string CSS => Mine ? "sent" : "received";
          }
      }
      
    3. @code 之前添加渲染部分,以便 UI 与 SignalR 客户端交互。Add rendering part before @code for UI to interact with SignalR client.

      <h1>Blazor SignalR Chat Sample</h1>
      <hr />
      
      @if (!_isChatting)
      {
          <p>
              Enter your name to start chatting:
          </p>
      
          <input type="text" maxlength="32" @bind="@_username" />
          <button type="button" @onclick="@Chat"><span class="oi oi-chat" aria-hidden="true"></span> Chat!</button>
      
          // Error messages
          @if (_message != null)
          {
              <div class="invalid-feedback">@_message</div>
              <small id="emailHelp" class="form-text text-muted">@_message</small>
          }
      }
      else
      {
          // banner to show current user
          <div class="alert alert-secondary mt-4" role="alert">
              <span class="oi oi-person mr-2" aria-hidden="true"></span>
              <span>You are connected as <b>@_username</b></span>
              <button class="btn btn-sm btn-warning ml-md-auto" @onclick="@DisconnectAsync">Disconnect</button>
          </div>
          // display messages
          <div id="scrollbox">
              @foreach (var item in _messages)
              {
                  @if (item.IsNotice)
                  {
                      <div class="alert alert-info">@item.Body</div>
                  }
                  else
                  {
                      <div class="@item.CSS">
                          <div class="user">@item.Username</div>
                          <div class="msg">@item.Body</div>
                      </div>
                  }
              }
              <hr />
              <textarea class="input-lg" placeholder="enter your comment" @bind="@_newMessage"></textarea>
              <button class="btn btn-default" @onclick="@(() => SendAsync(_newMessage))">Send</button>
          </div>
      }
      
  6. 更新 NavMenu.razor,在 NavMenuCssClass 下插入聊天室的输入菜单(如 REST)。Update NavMenu.razor to insert a entry menu for the chat room under NavMenuCssClass like rest.

    <li class="nav-item px-3">
        <NavLink class="nav-link" href="chatroom">
            <span class="oi oi-chat" aria-hidden="true"></span> Chat room
        </NavLink>
    </li>
    
  7. 更新 site.css 以优化图表区域气泡视图。Update site.css to optimize for chart area bubble views. 将代码追加到末尾。Append below code in the end.

    /* improved for chat text box */
    textarea {
        border: 1px dashed #888;
        border-radius: 5px;
        width: 80%;
        overflow: auto;
        background: #f7f7f7
    }
    
    /* improved for speech bubbles */
    .received, .sent {
        position: relative;
        font-family: arial;
        font-size: 1.1em;
        border-radius: 10px;
        padding: 20px;
        margin-bottom: 20px;
    }
    
    .received:after, .sent:after {
        content: '';
        border: 20px solid transparent;
        position: absolute;
        margin-top: -30px;
    }
    
    .sent {
        background: #03a9f4;
        color: #fff;
        margin-left: 10%;
        top: 50%;
        text-align: right;
    }
    
    .received {
        background: #4CAF50;
        color: #fff;
        margin-left: 10px;
        margin-right: 10%;
    }
    
    .sent:after {
        border-left-color: #03a9f4;
        border-right: 0;
        right: -20px;
    }
    
    .received:after {
        border-right-color: #4CAF50;
        border-left: 0;
        left: -20px;
    }
    
    /* div within bubble for name */
    .user {
        font-size: 0.8em;
        font-weight: bold;
        color: #000;
    }
    
    .msg {
        /*display: inline;*/
    }
    
  8. 单击 F5 运行应用。Click F5 to run the app. 你将可以如下所示进行聊天。You'll be able to chat like below.

    blazor-chat blazor-chat

发布到 AzurePublish to Azure

目前为止,Blazor 应用正在使用本地 SignalR,在部署到 Azure 应用服务时,建议使用 Azure SignalR 服务,该服务允许将 Blazor Server 应用扩展为大量的并发 SignalR 连接。So far, the Blazor App is working on local SignalR and when deploy to Azure App Service, it's suggested to use Azure SignalR Service which allows for scaling up a Blazor Server app to a large number of concurrent SignalR connections. 此外,SignalR 服务的全球覆盖和高性能数据中心可帮助显著减少由于地理位置造成的延迟。In addition, the SignalR service's global reach and high-performance data centers significantly aid in reducing latency due to geography.

重要

在 Blazor Server 应用中,UI 状态是在服务器端进行维护,这意味着在这种情况下需要确保服务器粘性。In Blazor Server app, UI states are maintained at server side which means server sticky is required in this case. 如果只有一个应用服务器,则服务器粘性可得到确保,这是设计使然。If there's single app server, server sticky is ensured by design. 但如果有多个应用服务器,则客户端协商和连接可能会转到不同服务器,并导致 Blazor 应用出现 UI 错误。However, if there're multiple app servers, there's a chance that client negotiation and connection may go to different servers and leads to UI errors in Blazor app. 因此,需要在 appsettings.json 中实现服务器粘性,如下所示:So you need to enable server sticky like below in appsettings.json:

"Azure:SignalR:ServerStickyMode": "Required"
  1. 右键单击该项目,然后导航到 PublishRight click the project and navigate to Publish.

    • 目标:AzureTarget: Azure
    • 特定目标:支持所有类型的 Azure 应用服务。Specific target: All types of Azure App Service are supported.
    • 应用服务:新建应用服务或选择现有的应用服务。App Service: create a new one or select existing app service.

    blazor-chat-profile blazor-chat-profile

  2. 添加 Azure SignalR 服务依赖项Add Azure SignalR Service dependency

    创建发布配置文件后,可以在“服务依赖项”下看到一条推荐消息。After publish profile created, you can see a recommended message under Service Dependencies. 单击“配置”以在面板中新建或选择现有的 Azure SignalR 服务。Click Configure to create new or select existing Azure SignalR Service in the panel.

    blazor-chat-dependency blazor-chat-dependency

    服务依赖项将执行以下操作,确保应用在 Azure 上时自动切换到 Azure SignalR 服务。The service dependency will do things below to enable your app automatically switch to Azure SignalR Service when on Azure.

    • 更新 HostingStartupAssembly 以使用 Azure SignalR 服务。Update HostingStartupAssembly to use Azure SignalR Service.
    • 添加 Azure SignalR 服务 NuGet 包引用。Add Azure SignalR Service NuGet package reference.
    • 更新配置文件属性以保存依赖项设置。Update profile properties to save the dependency settings.
    • 根据你的选择,配置机密存储。Configure secrets store depends on your choice.
    • 添加 appsettings 配置,确保应用面向所选的 Azure SignalR 服务。Add appsettings configuration to make your app target selected Azure SignalR Service.

    blazor-chat-dependency-summary blazor-chat-dependency-summary

  3. 发布应用Publish the app

    现已准备好发布。Now it's ready to publish. 发布完成后,应用将自动浏览页面。And it'll auto browser the page after publishing completes.

    备注

    由于 Azure 应用服务部署启动延迟,因此应用首次访问页面时可能无法立即正常工作,请尝试刷新页面,稍等一段时间。It may not immediately work in the first time visiting page due to Azure App Service deployment start up latency and try refresh the page to give some delay. 此外,你还可以使用浏览器调试器模式与 F12,以验证流量是否已重定向到 Azure SignalR 服务。Besides, you can use browser debugger mode with F12 to validate the traffic has already redirect to Azure SignalR Service.

    blazor-chat-azure blazor-chat-azure

延伸主题:在本地开发中启用 Azure SignalR 服务Further topic: Enable Azure SignalR Service in local development

  1. 添加对 Azure SignalR SDK 的引用Add reference to Azure SignalR SDK

    dotnet add package Microsoft.Azure.SignalR --version 1.5.1
    
  2. Startup.ConfigureServices() 中添加对 Azure SignalR 服务的调用。Add a call to Azure SignalR Service in Startup.ConfigureServices().

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddSignalR().AddAzureSignalR();
        ...
    }
    
  3. appsetting.json 中或使用机密管理器工具配置 Azure SignalR 服务 ConnectionStringConfigure Azure SignalR Service ConnectionString either in appsetting.json or with Secret Manager tool

备注

可以对 SignalR SDK 使用 HostingStartupAssembly 来替换步骤 2。Step 2 can be replaced by using HostingStartupAssembly to SignalR SDK.

  1. 添加配置以启用 appsetting.json 中的 Azure SignalR 服务Add configuration to turn on Azure SignalR Service in appsetting.json

    "Azure": {
      "SignalR": {
        "Enabled": true,
        "ServerStickyMode": "Required",
        "ConnectionString": <your-connection-string>
      }
    }
    
  2. 分配托管的启动程序集以使用 Azure SignalR SDK。Assign hosting startup assembly to use Azure SignalR SDK. 编辑 launchSettings.json 并在 environmentVariables 内添加如下所示的配置。Edit launchSettings.json and add a configuration like below inside environmentVariables.

    "environmentVariables": {
        ...,
        "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.Azure.SignalR"
      }
    

清理资源Clean up resources

若要清理本教程创建的资源,请使用 Azure 门户删除相应的资源组。To clean up the resources created in this tutorial, delete the resource group using the Azure portal.

后续步骤Next steps

本教程介绍以下操作:In this tutorial, you'll learn how to:

  • 使用 Blazor Server 应用构建简单的聊天室。Build a simple chat room with Blazor Server app.
  • 修改 Razor 组件。Modify Razor components.
  • 在组件中使用事件处理和数据绑定。Use event handling and data binding in components.
  • 在 Visual Studio 中快速部署到 Azure 应用服务。Quick deploy to Azure App Service in Visual Studio.
  • 将本地 SignalR 迁移到 Azure SignalR 服务。Migrate local SignalR to Azure SignalR Service.

深入了解高可用性。Read more about high availability.

其他资源Additional resources