クイック スタート:SignalR Service を使用してチャット ルームを作成する

Azure SignalR Service は、開発者がリアルタイムの機能を使って Web アプリケーションを簡単に作成できるようにするための Azure サービスです。

この記事では、Azure SignalR Service の使用を開始する方法について説明します。 このクイックスタートでは、ASP.NET Core Web アプリを使ってチャット アプリケーションを作成します。 このアプリでは、Azure SignalR Service との接続を確立して、リアルタイムのコンテンツ更新を可能にします。 Web アプリケーションをローカルでホストし、複数のブラウザー クライアントに接続します。 各クライアントは、他のすべてのクライアントにコンテンツ更新をプッシュできるようになります。

このクイック スタートの手順は、任意のコード エディターを使用して実行できます。 1 つのオプションは、Windows、macOS、および Linux プラットフォームで使用可能な Visual Studio Code です。

このチュートリアルのコードは、GitHub リポジトリの AzureSignalR-samples からダウンロードできます。 「SignalR Service の作成」のスクリプトに従って、このクイック スタートで使用される Azure リソースを作成できます。

Azure サブスクリプションをお持ちでない場合は、開始する前に無料アカウントを作成してください。

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

前提条件

問題がある場合は、 トラブルシューティング ガイドをお試しになるか、ご連絡ください

Azure SignalR リソースの作成

このセクションでは、アプリに使う基本的な Azure SignalR インスタンスを作成します。 次の手順では、Azure portal を使って新しいインスタンスを作成しますが、Azure CLI を使うこともできます。 詳細については、Azure SignalR Service CLI リファレンスaz signalr create コマンドを参照してください。

  1. Azure portal にサインインします。
  2. ページの左上にある [+ リソースの作成] を選択します。
  3. [リソースの作成] ページで、[Search services and marketplace](サービスとマーケットプレースを検索) テキスト ボックスに「signalr」と入力し、リストから [SignalR Service] を選びます。
  4. [SignalR Service] ページで [作成] を選びます。
  5. [基本] タブで、新しい SignalR Service のインスタンスに必要な情報を入力します。 次の値を入力します。
フィールド 推奨値 説明
サブスクリプション サブスクリプションの選択 新しい SignalR Service インスタンスを作成するために使うサブスクリプションを選びます。
リソース グループ SignalRTestResources というリソース グループを作成する SignalR リソースのリソース グループを選択または作成します。 既存のリソース グループを使う代わりに、このチュートリアルのために新しいリソース グループを作成すると便利です。 チュートリアルの終了後にリソースを解放するには、リソース グループを削除します。

リソース グループを削除すると、そのグループに所属するすべてのリソースも削除されます。 この削除操作は元に戻すことができません。 保存するリソースが含まれていないことを確認してから、リソース グループを削除してください。

詳細については、 リソース グループを使用した Azure リソースの管理に関するページを参照してください。
リソース名 testsignalr SignalR リソースに使用する一意のリソース名を入力します。 testsignalr がお使いのリージョンに既に導入されている場合は、数字や文字を追加して名前が一意になるようにします。

名前は 1 ~ 63 文字の文字列で、数字、英字、ハイフン (-) 文字のみを使用する必要があります。 名前の先頭と末尾にはハイフン文字を使用できません。また、連続するハイフン文字は無効です。
[リージョン] 自分のリージョンを選択します 新しい SignalR Service インスタンスに適切なリージョンを選びます。

Azure SignalR Service は、現在すべてのリージョンで使用できるわけではありません。 詳細については、Azure SignalR Service リージョンの可用性に関するページを参照してください
価格レベル [変更] を選び、[Free (Dev/Test Only)](無料 (Dev/Test のみ)) を選びます。 [選択] を選び、価格レベルを確認します。 Azure SignalR Service には、Free、Standard、Premium という 3 つの価格レベルがあります。 チュートリアルでは、前提条件で特に明記されない限り、[Free] レベルを使います。

レベルと価格による機能の違いの詳細については、「Azure SignalR Service の価格」を参照してください
サービス モード 適切なサービス モードを選びます Web アプリで SignalR のハブ ロジックをホストしていて、SignalR Service をプロキシとして使う場合は、[既定] を使います。 Azure Functions などのサーバーレス テクノロジを使って SignalR のハブ ロジックをホストする場合は、[サーバーレス] を使います。

[クラシック] モードは下位互換性のためだけにあり、使わないことをお勧めします。

詳細については、「Azure SignalR Service のサービス モード」を参照してください。

SignalR チュートリアルでは、[ネットワーク][タグ] タブの設定を変更する必要はありません。

  1. [基本] タブの下部にある [確認と作成] ボタンを選びます。
  2. [確認と作成] タブで、値を確認し [作成] を選びます。 デプロイが完了するまでしばらくかかります。
  3. デプロイが完了したら、[リソースに移動] ボタンを選びます。
  4. SignalR リソース ページで、左側のメニューから、[設定] の下にある [キー] を選びます。
  5. 主キーの [接続文字列] をコピーします。 この接続文字列は、このチュートリアルで後ほどアプリを構成するために必要です。

ASP.NET Core Web アプリケーションの作成

このセクションでは、.NET Core コマンド ライン インターフェイス (CLI) を使用して ASP.NET Core MVC Web アプリ プロジェクトを作成します。 Visual Studio ではなく .NET Core CLI を使用する利点は、Windows、macOS、および Linux プラットフォームで使用できることです。

  1. プロジェクトのフォルダーを作成します。 このクイックスタートでは、chattest フォルダーを使います。

  2. 新しいフォルダーで、次のコマンドを実行してプロジェクトを作成します。

    dotnet new web
    

プロジェクトにシークレット マネージャーを追加します

このセクションでは、プロジェクトにシークレット マネージャー ツールを追加します。 シークレット マネージャー ツールは、開発作業のための機密データをプロジェクト ツリーの外部に格納します。 このアプローチは、ソース コード内のアプリ シークレットが誤って共有されることを防止するのに役立ちます。

  1. フォルダーで、次のコマンドを実行して UserSecretsId を初期化します。

    dotnet user-secrets init
    
  2. シークレット マネージャーに、Azure:SignalR:ConnectionString という名前のシークレットを追加します。

    このシークレットには、SignalR Service リソースにアクセスするための接続文字列が含められます。 Azure:SignalR:ConnectionString は、SignalR が接続を確立するために検索する既定の構成キーです。 次のコマンドにある値を SignalR Service リソースの接続文字列に置き換えます。

    このコマンドは、 csproj ファイルと同じディレクトリで実行する必要があります。

    dotnet user-secrets set Azure:SignalR:ConnectionString "<Your connection string>"
    

    シークレット マネージャーは、Web アプリがローカルでホストされている間、そのアプリをテストするためにのみ使用されます。 後のチュートリアルでは、チャット Web アプリを Azure にデプロイします。 Web アプリが Azure にデプロイされた後は、シークレット マネージャーで接続文字列を格納する代わりに、アプリケーションの設定を使用します。

    このシークレットは、構成 API でアクセスされます。 構成名の中のコロン (:) は、サポートされているすべてのプラットフォーム上の構成 API で機能します。 環境別の構成に関するページを参照してください。

Web アプリに Azure SignalR を追加する

  1. 次のコマンドを実行して、Microsoft.Azure.SignalR NuGet パッケージへの参照を追加します。

    dotnet add package Microsoft.Azure.SignalR
    
  2. Program.cs を開き、コードを次のように更新して、AddSignalR() メソッドと AddAzureSignalR() メソッドを呼び出して Azure SignalR Service を使用します。

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddSignalR().AddAzureSignalR();
    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseRouting();
    app.UseStaticFiles();
    app.MapHub<ChatSampleHub>("/chat");
    app.Run();
    

    AddAzureSignalR() にパラメーターを渡していないため、SignalR Service リソースの接続文字列に既定の構成キーが使用されます。 既定の構成キーは Azure:SignalR:ConnectionString です。 また、以下のセクションで作成する ChatSampleHub も使用されます。

ハブ クラスの追加

SignalR では、"ハブ" は、クライアントから呼び出すことができる一連のメソッドを公開するコア コンポーネントです。 このセクションでは、次の 2 つのメソッドでハブ クラスを定義します。

  • BroadcastMessage:このメソッドは、すべてのクライアントにメッセージをブロードキャストします。
  • Echo:このメソッドは、メッセージを呼び出し元に返送します。

どちらのメソッドも、ASP.NET Core SignalR SDK によって提供される Clients インターフェイスを使用します。 このインターフェイスにより、接続されているすべてのクライアントにアクセスできるため、クライアントにコンテンツをプッシュできます。

  1. プロジェクト ディレクトリに、Hub という名前の新しいフォルダーを追加します。 新しいフォルダーに、ChatSampleHub.cs という名前の新しいハブ コード ファイルを追加します。

  2. 次のコードを ChatSampleHub.cs に追加してハブ クラスを定義し、ファイルを保存します。

    using Microsoft.AspNetCore.SignalR;
    
    public class ChatSampleHub : Hub
    {
        public Task BroadcastMessage(string name, string message) =>
            Clients.All.SendAsync("broadcastMessage", name, message);
    
        public Task Echo(string name, string message) =>
            Clients.Client(Context.ConnectionId)
                    .SendAsync("echo", name, $"{message} (echo from server)");
    }
    

Web アプリのクライアント インターフェイスを追加する

このチャット ルーム アプリのクライアント ユーザー インターフェイスは、wwwroot ディレクトリ内の index.html という名前のファイルに含まれている HTML と JavaScript で構成されます。

css/site.css ファイルをサンプル リポジトリwwwroot フォルダーからコピーします。 プロジェクトの css/site.css を、コピーしたものに置き換えます。

wwwroot ディレクトリに index.html という名前の新しいファイルを作成し、次の HTML をコピーして、新しく作成したファイルに貼り付けます。

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
  <meta name="viewport" content="width=device-width">
  <meta http-equiv="Pragma" content="no-cache" />
  <meta http-equiv="Expires" content="0" />
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet" />
  <link href="css/site.css" rel="stylesheet" />
  <title>Azure SignalR Group Chat</title>
</head>
<body>
  <h2 class="text-center" style="margin-top: 0; padding-top: 30px; padding-bottom: 30px;">Azure SignalR Group Chat</h2>
  <div class="container" style="height: calc(100% - 110px);">
    <div id="messages" style="background-color: whitesmoke; "></div>
    <div style="width: 100%; border-left-style: ridge; border-right-style: ridge;">
      <textarea id="message" style="width: 100%; padding: 5px 10px; border-style: hidden;"
        placeholder="Type message and press Enter to send..."></textarea>
    </div>
    <div style="overflow: auto; border-style: ridge; border-top-style: hidden;">
      <button class="btn-warning pull-right" id="echo">Echo</button>
      <button class="btn-success pull-right" id="sendmessage">Send</button>
    </div>
  </div>
  <div class="modal alert alert-danger fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <div>Connection Error...</div>
          <div><strong style="font-size: 1.5em;">Hit Refresh/F5</strong> to rejoin. ;)</div>
        </div>
      </div>
    </div>
  </div>

  <!--Reference the SignalR library. -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

  <!--Add script to update the page and send messages.-->
  <script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function () {
      function getUserName() {
        function generateRandomName() {
          return Math.random().toString(36).substring(2, 10);
        }

        // Get the user name and store it to prepend to messages.
        var username = generateRandomName();
        var promptMessage = "Enter your name:";
        do {
          username = prompt(promptMessage, username);
          if (!username || username.startsWith("_") || username.indexOf("<") > -1 || username.indexOf(">") > -1) {
            username = "";
            promptMessage = "Invalid input. Enter your name:";
          }
        } while (!username)
        return username;
      }

      username = getUserName();
      // Set initial focus to message input box.
      var messageInput = document.getElementById("message");
      messageInput.focus();

      function createMessageEntry(encodedName, encodedMsg) {
        var entry = document.createElement("div");
        entry.classList.add("message-entry");
        if (encodedName === "_SYSTEM_") {
          entry.innerHTML = encodedMsg;
          entry.classList.add("text-center");
          entry.classList.add("system-message");
        } else if (encodedName === "_BROADCAST_") {
          entry.classList.add("text-center");
          entry.innerHTML = `<div class="text-center broadcast-message">${encodedMsg}</div>`;
        } else if (encodedName === username) {
          entry.innerHTML = `<div class="message-avatar pull-right">${encodedName}</div>` +
            `<div class="message-content pull-right">${encodedMsg}<div>`;
        } else {
          entry.innerHTML = `<div class="message-avatar pull-left">${encodedName}</div>` +
            `<div class="message-content pull-left">${encodedMsg}<div>`;
        }
        return entry;
      }

      function appendMessage(encodedName, encodedMsg) {
        var messageEntry = createMessageEntry(encodedName, encodedMsg);
        var messageBox = document.getElementById("messages");
        messageBox.appendChild(messageEntry);
        messageBox.scrollTop = messageBox.scrollHeight;
      }

      function bindConnectionMessage(connection) {
        var messageCallback = function (name, message) {
          if (!message) return;
          // Html encode display name and message.
          var encodedName = name;
          var encodedMsg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
          appendMessage(encodedName, encodedMsg);
        };
        // Create a function that the hub can call to broadcast messages.
        connection.on("broadcastMessage", messageCallback);
        connection.on("echo", messageCallback);
        connection.onclose(onConnectionError);
      }

      function onConnected(connection) {
        console.log("connection started");
        connection.send("broadcastMessage", "_SYSTEM_", username + " JOINED");
        document.getElementById("sendmessage").addEventListener("click", function (event) {
          // Call the broadcastMessage method on the hub.
          if (messageInput.value) {
            connection.send("broadcastMessage", username, messageInput.value)
              .catch((e) => appendMessage("_BROADCAST_", e.message));
          }

          // Clear text box and reset focus for next comment.
          messageInput.value = "";
          messageInput.focus();
          event.preventDefault();
        });
        document.getElementById("message").addEventListener("keypress", function (event) {
          if (event.keyCode === 13) {
            event.preventDefault();
            document.getElementById("sendmessage").click();
            return false;
          }
        });
        document.getElementById("echo").addEventListener("click", function (event) {
          // Call the echo method on the hub.
          connection.send("echo", username, messageInput.value);

          // Clear text box and reset focus for next comment.
          messageInput.value = "";
          messageInput.focus();
          event.preventDefault();
        });
      }

      function onConnectionError(error) {
        if (error && error.message) {
          console.error(error.message);
        }
        var modal = document.getElementById("myModal");
        modal.classList.add("in");
        modal.style = "display: block;";
      }

      var connection = new signalR.HubConnectionBuilder()
        .withUrl("/chat")
        .build();
      bindConnectionMessage(connection);
      connection.start()
        .then(function () {
          onConnected(connection);
        })
        .catch(function (error) {
          console.error(error.message);
        });
    });
  </script>
</body>
</html>

index.html 内のコードは、HubConnectionBuilder.build() を呼び出して Azure SignalR リソースへの HTTP 接続を作成します。

接続が成功すると、その接続が bindConnectionMessage に渡され、受信されたコンテンツ プッシュ用のイベント ハンドラーがクライアントに追加されます。

HubConnection.start() は、ハブとの通信を開始します。 次に、onConnected() がボタンのイベント ハンドラーを追加します。 これらのハンドラーが接続を使用することで、そのクライアントからすべての接続済みクライアントにコンテンツ更新をプッシュできるようになります。

アプリをビルドしてローカルで実行する

  1. 次のコマンドを実行して、Web アプリをローカルで実行します。

    dotnet run
    

    アプリはローカルでホストされ、たとえば次のような localhost URL を含む出力が生成されます。

    Building...
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: http://localhost:5000
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Development
    
  2. 2 つのブラウザー ウィンドウを開きます。 各ブラウザーで、出力ウィンドウに表示されている localhost URL (たとえば、上記の出力ウィンドウに表示されている http://localhost:5000/) に移動します。 名前を入力するよう求められます。 両方のクライアントのクライアント名を入力し、 [Send] ボタンを使用して、両方のクライアント間でのメッセージ コンテンツのプッシュをテストします。

    Example of an Azure SignalR group chat

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

次のチュートリアルに進む場合は、このクイック スタートで作成されたリソースを保持し、それを再利用できます。

クイック スタートのサンプル アプリケーションを完了した場合は、料金がかからないように、このクイック スタートで作成された Azure リソースを削除できます。

重要

リソース グループの削除は元に戻せず、そのグループ内のすべてのリソースが含まれます。 間違ったリソース グループやリソースをうっかり削除しないようにしてください。 このサンプルのリソースを、保持したいリソースを含む既存のリソース グループ内に作成した場合は、リソース グループを削除する代わりに、各リソースをそのブレードから個別に削除できます。

Azure portal にサインインし、 [リソース グループ] を選択します。

[名前でフィルター] テキスト ボックスに、リソース グループの名前を入力します。 このクイックスタートの手順では、SignalRTestResources という名前のリソース グループを使用しました。 結果の一覧の中のリソース グループで、省略記号 ([...]) >[リソース グループの削除] を選びます。

Selections for deleting a resource group

リソース グループの削除の確認を求めるメッセージが表示されます。 確認のためにリソース グループの名前を入力し、 [削除] を選択します。

しばらくすると、リソース グループとそのリソースのすべてが削除されます。

問題がある場合は、 トラブルシューティング ガイドをお試しになるか、ご連絡ください

次のステップ

このクイック スタートでは、新しい Azure SignalR Service リソースを作成しました。 その後、それを ASP.NET Core Web アプリで使用して、コンテンツの更新を接続されている複数のクライアントにリアルタイムにプッシュしました。 Azure SignalR Service の使用についてさらに学習するには、認証をデモンストレーションするチュートリアルに進んでください。