チュートリアル:Blazor Server チャット アプリを構築する

このチュートリアルでは、Blazor Server アプリの構築と変更を行う方法について説明します。 学習内容は次のとおりです。

  • Blazor Server アプリ テンプレートを使用して簡単なチャット ルームを作成する。
  • Razor コンポーネントを操作する。
  • Razor コンポーネントのイベント処理とデータ バインディングを使用する。
  • Visual Studio で Azure App Service にすばやくデプロイする。
  • ローカルの SignalR から Azure SignalR Service に移行する。

開始の準備はできていますか?

前提条件

問題がある場合は、 ぜひご意見をお寄せください。

Blazor Server アプリでローカル チャット ルームを作成する

Visual Studio 2019 バージョン 16.2.0 以降では、Azure SignalR Service が Web アプリケーションの発行プロセスに組み込まれているため、Web アプリと SignalR Service との間の依存関係の管理がはるかに容易になっています。 ローカル開発環境のローカル SignalR インスタンスでの作業と、Azure App Service 向けの Azure SignalR Service での作業を、コードに変更を加えることなく同時に行うことができます。

  1. Blazor チャット アプリを作成します。

    1. Visual Studio で、 [新しいプロジェクトの作成] を選択します。

    2. [Blazor アプリ] を選択します。

    3. アプリケーションに名前を付け、フォルダーを選択します。

    4. [Blazor Server App] (Blazor Server アプリ) テンプレートを選択します。

      Note

      Visual Studio でターゲット フレームワークが正しく認識されるよう、あらかじめ .NET Core SDK 3.0 以上をインストールしておいてください。

      In Create a new project, select the Blazor app template.

    5. .NET CLI で dotnet new コマンドを実行してプロジェクトを作成することもできます。

      dotnet new blazorserver -o BlazorChat
      
  2. BlazorChatSampleHub.cs という名前の新しい C# ファイルを追加し、チャット アプリの Hub クラスから派生した新しいクラス BlazorChatSampleHub を作成します。 ハブの作成の詳細については、「ハブの作成と使用」を参照してください。

    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() メソッドにハブのエンドポイントを追加します。

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub();
        endpoints.MapFallbackToPage("/_Host");
        endpoints.MapHub<BlazorChatSampleHub>(BlazorChatSampleHub.HubUrl);
    });
    
  4. SignalR クライアントを使用するための Microsoft.AspNetCore.SignalR.Client パッケージをインストールします。

    dotnet add package Microsoft.AspNetCore.SignalR.Client --version 3.1.7
    
  5. SignalR クライアントを実装するために、Pages フォルダーの下に ChatRoom.razor という名前の新しい Razor コンポーネントを作成します。 以下の手順に従うか、ChatRoom.razor ファイルを使用してください。

    1. @page ディレクティブと using ステートメントを追加します。 @inject ディレクティブを使用して、NavigationManager サービスを挿入します。

      @page "/chatroom"
      @inject NavigationManager navigationManager
      @using Microsoft.AspNetCore.SignalR.Client;
      
    2. メッセージを送受信するために、@code セクションで、次のメンバーを新しい SignalR クライアントに追加します。

      @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
              InvokeAsync(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. SignalR クライアントと対話するために、@code セクションの前に UI マークアップを追加します。

      <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 の下のチャット ルームにリンクする新しい NavLink コンポーネントを挿入します。

    <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 ファイルにいくつかの CSS クラスを追加して、チャット ページの UI 要素のスタイルを設定します。

    /* 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 キーを押して、アプリを実行します。 これで、チャットを開始できます。

    An animated chat between Bob and Alice is shown. Alice says Hello, Bob says Hi.

問題がある場合は、 ぜひご意見をお寄せください。

Azure に発行する

Azure App Service に Blazor アプリをデプロイする場合は、Azure SignalR Service を使用することをお勧めします。 Azure SignalR Service を使うと、Blazor Server アプリを多数の同時 SignalR 接続にスケールアップできます。 さらに、SignalR Service のグローバル リーチとハイパフォーマンスのデータセンターは、地理的条件による待ち時間の短縮に大きく役立ちます。

重要

Blazor Server アプリでは、UI の状態はサーバー側で保持されます。つまり、状態を保持するには、スティッキー サーバー セッションが必要です。 アプリ サーバーが 1 台の場合は、設計によってスティッキー セッションが保証されます。 これに対し、複数のアプリ サーバーがある場合は、クライアントのネゴシエーションと接続に異なるサーバーが使用された結果、Blazor アプリの UI 状態管理に一貫性がなくなる可能性があります。 そのため、次に示すように、appsettings.json 内でスティッキー サーバー セッションを有効にすることをお勧めします。

"Azure:SignalR:ServerStickyMode": "Required"
  1. プロジェクトを右クリックし、 [発行] を選択します。 次の設定を使用します。

    • ターゲット: Azure
    • 特定のターゲット: すべての種類の Azure App Service がサポートされています。
    • App Service: App Service インスタンスを作成または選択します。

    The animation shows selection of Azure as target, and then Azure App Serice as specific target.

  2. Azure SignalR Service の依存関係を追加します。

    発行プロファイルの作成後、 [サービスの依存関係] に Azure SignalR Service を追加することを提案するメッセージが表示されます。 このペインで [構成] を選択して、既存の Azure SignalR Service を選択するか、新しく作成してください。

    On Publish, the link to Configure is highlighted.

    Azure SignalR Service が Azure 上にあればアプリがそれに自動的に切り替わるよう、サービス依存関係によって以下のアクティビティが実行されます。

    • Azure SignalR Service を使用するように HostingStartupAssembly を更新する。
    • Azure SignalR Service NuGet パッケージの参照を追加する。
    • プロファイル プロパティを更新して、依存関係の設定を保存する。
    • 選択内容に従ってシークレット ストアを構成する。
    • Azure SignalR Service がアプリのターゲットとなるように appsettings.json に構成を追加する。

    On Summary of changes, the checkboxes are used to select all dependencies.

  3. アプリの発行

    これで、アプリを発行する準備が整いました。 発行プロセスが完了すると、ブラウザー内でアプリが自動的に起動されます。

    Note

    Azure App Service のデプロイが開始されるまでの待ち時間が原因で、アプリの起動に時間がかかる場合があります。 (通常は F12 キーを押して) ブラウザーのデバッガー ツールを使用すると、トラフィックが Azure SignalR Service にリダイレクトされていることを確認できます。

    Blazor SignalR Chat Sample has a text box for your name, and a Chat! button to start a chat.

問題がある場合は、 ぜひご意見をお寄せください。

ローカル開発用に Azure SignalR Service を有効にする

  1. 次のコマンドを使用して、Azure SignalR SDK への参照を追加します。

    dotnet add package Microsoft.Azure.SignalR
    
  2. 次に示すように、AddAzureSignalR() の呼び出しを Startup.ConfigureServices() に追加します。

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddSignalR().AddAzureSignalR();
        ...
    }
    
  3. appsettings.json 内で、またはシークレット マネージャー ツールを使用して、Azure SignalR Service の接続文字列を構成します。

Note

手順 2 の代わりに、SignalR SDK を使用するようにホスティング スタートアップ アセンブリを構成することができます。

  1. Azure SignalR Service を有効にするための構成を appsettings.json に追加します。

    "Azure": {
      "SignalR": {
        "Enabled": true,
        "ConnectionString": <your-connection-string>       
      }
    }
    
    
  2. Azure SignalR SDK を使用するようにホスティング スタートアップ アセンブリを構成します。 launchSettings.json を編集して、environmentVariables 内に次の例のような構成を追加します。

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

問題がある場合は、 ぜひご意見をお寄せください。

リソースをクリーンアップする

このチュートリアルで作成したリソースをクリーンアップするには、Azure portal を使用してリソース グループを削除します。

その他のリソース

次のステップ

このチュートリアルでは、以下の内容を学習しました。

  • Blazor Server アプリ テンプレートを使用して簡単なチャット ルームを作成する。
  • Razor コンポーネントを操作する。
  • Razor コンポーネントのイベント処理とデータ バインディングを使用する。
  • Visual Studio で Azure App Service にすばやくデプロイする。
  • ローカルの SignalR から Azure SignalR Service に移行する。

高可用性についての詳細を参照してください。