チャットアプリケーションを構築する
ここから、アプリケーションを作っていきましょう。このアプリは、サーバに接続した複数のクライアント(Webブラウザ)間で、入力した名前とメッセージを共有してやり取りするというものです(図3)。クライアントが送信したメッセージが、ハブを介して全クライアントに送信されるという流れだけに絞って見るために、メッセージの保存は行いません。アプリケーションの名前はSignalRChatSampleとします。
![図3:チャットルームアプリの外観](http://cz-cdn.shoeisha.jp/static/images/article/18494/18494_03.png)
クライアントの実装は、リアルタイム性に優れてHTML(Razorページ)との連携も容易なJavaScriptを使用します。これを踏まえたアプリケーションの構成は図4の通りです。ハブ(C#)、クライアント(JavaScript)、Razorページ(.cshtml)、そしてスタートアップコード(Program.cs)のそれぞれを作成、修正していくことで目的のアプリケーションを作成します。以降の説明において、適宜図4を参照してください。
![図4:チャットルームアプリの構成](http://cz-cdn.shoeisha.jp/static/images/article/18494/18494_4.png)
アプリケーションの作成
SignalRアプリケーションのためのテンプレートは用意されていませんので、ここでは通常のRazor Pagesアプリケーションを作成します。以下のdotnet newコマンドで作成します。
% dotnet new webapp -o SignalRChatSample -f net7.0
このままでは第2回で紹介したRazor Pagesアプリケーションと変わりません。ただちに実行できますが、既定のページが表示されるだけです。ここに、SignalRによるチャットアプリのためのライブラリ、ファイル、コードを追加していきます。なお、-fオプションは本稿作成時点での最新バージョンである.NET 7をターゲットにする指定です。
クライアントライブラリのインストール
SignalRをクライアントから利用するためのライブラリをプロジェクトにインストールします。インストールには、ASP.NET CoreのクライアントライブラリマネージャであるLibManを使用します。LibManがインストールされていない場合(本連載では初出)、以下のコマンドでLibManのCLIツールをインストールします。
% dotnet tool install -g Microsoft.Web.LibraryManager.Cli 次のコマンドを使用してツールを呼び出せます。libman ツール 'microsoft.web.librarymanager.cli' (バージョン '2.1.175') が正常にインストールされました。
ここで、プロジェクトのルートであるSignalRChatSampleフォルダに移動して、SignalRのためのクライアントライブラリをLibManのCLIツールであるlibmanコマンドでインストールします。
% libman install @microsoft/signalr@latest -p unpkg -d wwwroot/js/signalr --files dist/browser/signalr.js ファイル https://unpkg.com/@microsoft/signalr@latest/dist/browser/signalr.js をダウンロードしています... wwwroot/js/signalr/dist/browser/signalr.js はディスクに書き込まれました ライブラリ "@microsoft/signalr@latest" が "wwwroot/js/signalr" にインストールされました
libmanのinstallサブコマンドで、引数で指定する(この場合は@microsoft/signalr)npmパッケージをインストールします。指定したオプションは以下の通りです。
- -p:CDN(Content Delivery Network)などのプロバイダの指定(この場合はunpkg)
- -d:書き込み先(wwwroot/js/signalr)の指定
- --files:ダウンロードファイル(dist/browser/signalr.js)の指定
ハブの作成
ここからはコードの作成と修正です。まずは、ハブを新規に作成します。プロジェクトのルートにHubフォルダを作成し、そこにChatRoomHub.csファイルを作成します。そして、このファイルにリストの内容を記述します。
using Microsoft.AspNetCore.SignalR; (1) namespace SignalRChatSample.Hubs (2) { public class ChatRoomHub : Hub (3) { public async Task TransmitMessage(string sender, string message) (4) { DateTime dt = DateTime.Now; await Clients.All.SendAsync("ReceiveMessage", sender, message, dt.ToString("yyyy/MM/dd HH:mm:ss")); (5) } } }
(1)は、SignalRのサーバライブラリの使用のために必要な宣言です。このように、SignalRのサーバ側のライブラリはASP.NETのコアライブラリに含まれており、宣言だけで利用できます。
(2)では、ハブクラスを含める名前空間SignalRChatSample.Hubsを宣言しています。アプリケーションが複数のハブクラスを持つなら、同じ名前空間に配置するようにします。また、後述するProgram.csファイルでこの名前空間を参照するので、同じ名前空間になるようにします。
(3)では、チャットルームのハブクラスを定義しています。ハブクラスは、Hubクラスを継承することになっています。
(4)では、非同期メソッドTransmitMessageを定義しています。このメソッドは、クライアントからのリクエストに応じて呼び出されて、必要な処理を行います。(5)は、この処理内容であり、SendAsyncメソッドが実行されます。Clientsオブジェクトは文字通りクライアントを意味し、Allプロパティはその全てを表します。つまり、全クライアントに対して非同期にクライアントメソッド名"ReceiveMessage"とともに引数(名前、メッセージ、現在日時)を全て送信せよという意味になります。"ReceiveMessage"はクライアントのメソッドに相当し、この名前で定義されたメソッドが各クライアントで呼び出されます。
[NOTE]送信先の選択
このサンプルでは、送信者を含めた全クライアントにメッセージを送信しています(All.SendAsyncメソッド)。このほかに、送信者自身(Caller.SendAsyncメソッド)、送信者以外(Others.SendAsyncメソッド)に送信するメソッドも用意されており、用途に応じて使い分けることができます。
チャットルームページの作成
チャットルームのページは、アプリケーションのルートページすなわちIndex.cshtmlに置くことにします。既定では、このページはメニューとタイトルだけなので、そこにチャットの入力フォームとチャット履歴を表示するリストを配置します。
@page @{ ViewData["Title"] = "チャットルーム"; } <div class="container"> <div class="row p-1"> (1) <div class="col-2">お名前</div> <div class="col-6"><input type="text" class="w-50" id="sender" /></div> </div> <div class="row p-1"> <div class="col-2">メッセージ</div> <div class="col-6"><input type="text" class="w-100" id="message" /></div> </div> <div class="row p-1"> <div class="col-8"> <input type="button" id="submit" value="送信!" disabled /> </div> </div> <div class="row p-1"> <div class="col-8"> <hr /> </div> </div> <div class="row p-1"> (2) <div class="col-8"> <ul id="messages" class="list-unstyled"></ul> </div> </div> </div> <script src="~/js/signalr/dist/browser/signalr.js"></script> (3) <script src="~/js/chatroom.js"></script>
(1)は、送信者の名前とメッセージと送信ボタンからなるフォームとなっています。名前、メッセージ、ボタンのid属性はそれぞれ後述するJavaScriptコードから参照されます。
(2)は、チャット履歴の表示場所です。表示にはulタグ(リストマーカーなし)を使用しています。このid属性も、後述するJavaScriptコードから参照されます。
(3)からは、それぞれSignalRのクライアントライブラリsignalr.jsと、後述するクライアントコードであるchatroom.jsを、それぞれ読み込んでいます。
クライアントコードの作成
上記のページで読み込まれるchatroom.jsをwwwroot/jsフォルダ以下に新規に作成し、クライアントのコードを書いていきます。
"use strict"; let conn = new signalR.HubConnectionBuilder().withUrl("/chatRoomHub") .build(); (1) conn.start().then(() => { (2) document.getElementById("submit").disabled = false; }).catch((err) => { return console.error(err.toString()); }); conn.on("ReceiveMessage", (sender, message, dt) => { (3) let li = document.createElement("li"); document.getElementById("messages").insertAdjacentElement('afterbegin', li); li.textContent = `${dt} [${sender}] ${message}`; }); document.getElementById("submit").addEventListener("click", (e) => { (4) let sender = document.getElementById("sender").value; let message = document.getElementById("message").value; conn.invoke("TransmitMessage", sender, message).catch((err) => { return console.error(err.toString()); }); e.preventDefault(); });
(1)では、ハブ接続オブジェクトを生成しています。withUrlメソッドは、引数のURLで接続するハブクラスの指定を行います。URLとハブクラスのマッピングは、後述するProgram.cs中のMapHubメソッドによって行います。
(2)は、コネクション開始時にコールバックされる関数です。処理内容は、ボタンを有効化するだけですが、必要に応じて入力フォームの準備などの処理を行わせることができます。
(3)は、ハブのSendAsyncメソッドの引数で指定したクライアントメソッド"ReceiveMessage"に対応するコールバック関数の定義です。処理内容は、3つの引数(送信者名、メッセージ、現在日時)を受け取って、それをもとにli要素を生成してフォームのチャット履歴の上端に挿入します。
(4)は、ボタンクリック時のイベントハンドラの定義です。名前、メッセージを取り出し、ハブ接続オブジェクトのinvokeメソッドを使って、ハブのTransmitMessageメソッドを呼び出します。
スタートアップコードの修正
最後に、スタートアップコードであるProgram.csファイルにSignalR利用のための追記を行います。
using SignalRChatSample.Hubs; (1) var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddSignalR(); (2) …略… app.MapRazorPages(); app.MapHub<ChatRoomHub>("/chatRoomHub"); (3) app.Run();
(1)には、ハブのコードと同様の名前空間を指定します。これは、(3)でハブクラスの名前を参照するためです。
(2)では、SignalRをサービスとして登録しています。これは、SignalRを使うアプリケーションで必須の指定です。
(3)では、エンドポイント/chatRoomHubへのリクエストをChatRoomHubクラスにマップしています(JavaScriptからの呼び出しなので相対パスでよい)。この記述により、/chatRoomHubをエンドポイントとする通信はChatRoomHubクラスによって処理されるようになります。ここで指定するエンドポイントが、chatroom.jsファイルの(1)においてwithUrlメソッドの引数にもなっていることを思い出してください。なお、以下のようにMapHubメソッドの引数にoptions.Transportsを指定することで、使用するトランスポートを明示的に指定することができます(この場合はWebSocketとサーバ送信イベント)。
app.MapHub<ChatRoomHub>("/chatRoomHub", options => { options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents; });
動作確認
ここでdotnet watchコマンドを実行すると、チャットルームアプリを起動し、Webブラウザを自動的に開きます。図3のようにWebブラウザのウィンドウを複数開き、相互にメッセージを入力して同じチャット履歴が表示されれば成功です。
% dotnet watch dotnet watch 🚀 Started info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5006 …略…
まとめ
今回は、ASP.NET CoreにおけるSignalRの概要と、シンプルなチャットルームを実現するアプリケーションの作成を通じてSignalRの機能を見てきました。非常にシンプルなコードで、リアルタイム性の必要なアプリケーションを構築できることをご理解いただけたのではないかと思います。
今回を以て、最新.NETにおけるASP.NET Coreの利用を紹介する連載は終了です。.NET 6を対象に始まった連載も、その途中で.NET 7のリリースを迎えたように、.NETは常に進化しています。.NETにおけるWeb開発に興味を持った方、今のASP.NET Coreの姿を知りたいという方にとって少しでも有益な情報をお届けできたなら幸いです。