次の方法で共有


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

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

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

重要

この記事に示す生の接続文字列は、デモンストレーションのみを目的としています。

接続文字列には、アプリケーションが Azure Web PubSub サービスにアクセスするために必要な認可情報が含まれています。 接続文字列内のアクセス キーは、サービスのルート パスワードに似ています。 運用環境では、常にアクセス キーを保護してください。 Azure Key Vault を使用してキーの管理とローテーションを安全に行い、Microsoft Entra ID を使用して接続文字列をセキュリティで保護し、Microsoft Entra ID を使用してアクセスを認可します。

アクセス キーを他のユーザーに配布したり、ハードコーディングしたり、他のユーザーがアクセスできるプレーンテキストで保存したりしないでください。 キーが侵害された可能性があると思われる場合は、キーをローテーションしてください。

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

前提条件

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

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 以上をインストールしておいてください。

      [新しいプロジェクトの作成] で、Blazor アプリ テンプレートを選択します。

    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 キーを押します。 これで、チャットを開始できます。

    Bob と Alice との間で交わされたチャットのアニメーションが表示されています。Alice が

問題がありますか。 ぜひご意見をお寄せください。

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 インスタンスを作成または選択します。

    このアニメーションでは、[ターゲット] として Azure を選んでから、[特定のターゲット] として Azure App Service を選んでいます。

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

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

    [発行] で、[構成] へのリンクが強調表示されています。

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

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

    [Summary of changes]\(変更の概要\) で、チェック ボックスを使用してすべての依存関係が選択されています。

  3. アプリの発行

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

    Note

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

    Blazor SignalR Chat Sample には、自分の名前を入力するためのテキスト ボックスと、会話を開始するための [Chat!] ボタンがあります。

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

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

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

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

    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 Key Vault を使用してキーの管理とローテーションを安全に行い、Microsoft Entra ID を使用して接続文字列をセキュリティで保護し、Microsoft Entra ID を使用してアクセスを認可します。

    "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 に移行する。

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