最近SignalRが気になったので公式チュートリアルやってみました。
分かりやすくていいんだけどリアルタイムアプリにありがちなチャットルーム毎に通信を分ける機能がなかったので自分で実装してみました。
余計な機能はつけずに、最低限ルーム内通信が成り立つ程度のシンプルな実装にしています。
尚、ルーム内通信を実現するのにSignalRのグループ機能を利用しています。
実装済みのコードはGitHubに上げてます。
バージョン
- Visual Studio 2019
- .NET 5.0
プロジェクト作成
基本公式チュートリアル 通りです。
- Visual Studioで新しいプロジェクトを作成する
- 「ASP.NET Core Webアプリ」を選択する
- ※同じVS2019でもインストール環境によっては名称が少し違うことがあります。
- プロジェクト名は「SignalRGroupChat」とした
とりあえず実行
プロジェクトが作成出来たら、まずは初期状態で動くことを確認するため「F5」キーを押して実行します。
SignalRクライアントライブラリを追加する
公式チュートリアル 通りです。
- ソリューションエクスプローラーで、プロジェクトを右クリックし、[追加] -> [クライアント側のライブラリ]を選択する
- [クライアント側のライブラリを追加します]ダイアログで以下の通り選択する
- プロバイダー:
unpkg
- ライブラリ:
@microsoft/signalr@latest
- [特定のファイルの選択]を選択する
dist/browser
フォルダを展開し、signalr.js
とsignalr.min.js
を選択する- [ターゲットロケーション]に
wwwroot/js/signalr/
と入力する - [インストール]をクリック
- プロバイダー:
SignalRハブを作成する
基本は公式チュートリアル 通りだけど、ハブに実装するコードについてはグループ機能に関する処理に書き換えます。
- プロジェクト直下に
Hubs
フォルダを作成する Hubs
フォルダ内にChatHub.cs
を作成し、以下のように編集する
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace SignalRGroupChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessageToGroup(string group, string user, string message)
{
await Clients.Group(group).SendAsync("ReceiveMessage", user, message);
}
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
}
ちょっと説明
SendMessageToGroup
関数について- チュートリアルでは全クライアントに送信するが、今回は指定したグループに送信する(参考)
AddToGroup
関数について- クライアントをグループに追加する処理(参考)
SignalRを構成する
公式チュートリアル 通りです。
Startup.cs
を以下のように変更する
using SignalRGroupChat.Hubs;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR();
}
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/chatHub");
});
}
SignalRクライアントコードを追加する
ここは公式チュートリアルとは少し異なります。
まず、トップページはグループ名を入力してもらう画面にします。
そして、チャットルームページを新たに作成してチャット画面を実装します。
Pages\Index.cshtml
を以下のように変更する
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="container">
<div class="row">
<div class="col-2">Room name</div>
<div class="col-4"><input type="text" id="roomName" /></div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-6">
<input type="button" id="moveToRoom" value="Move to room" />
</div>
</div>
</div>
<script src="~/js/index.js"></script>
wwwroot\js\index.js
を作成し、以下のように編集する
document.getElementById("moveToRoom").addEventListener("click", function (event) {
var roomName = document.getElementById("roomName").value;
window.location.href = "Room/" + roomName;
});
Pages\Room.cshtml
を作成し、以下のように編集する- VSを使っている場合はソリューションエクスプローラーで
Pages
フォルダを右クリックして[追加] -> [新しい項目] -> [Razorページ – 空]を選択すると良い(C#コードビハインドを自動生成してくれる)
- VSを使っている場合はソリューションエクスプローラーで
@page "{roomname?}"
@model RoomModel
@{
ViewData["Title"] = "Room";
}
<h1>@ViewData["Title"] : @Model.RoomName</h1>
<input type="hidden" id="roomName" value="@Model.RoomName" />
<div class="container">
<div class="row"> </div>
<div class="row">
<div class="col-2">User</div>
<div class="col-4"><input type="text" id="userInput" /></div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" /></div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send Message" />
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>
Pages\Room.cshtml.cs
を以下のように変更する
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace SignalRGroupChat.Pages
{
public class RoomModel : PageModel
{
public string RoomName;
public void OnGet(string roomName)
{
RoomName = roomName;
}
}
}
ちょっと説明
ここにはURLの末尾をルーム名として設定する処理を書いています。
wwwroot\js\chat.js
を作成し、以下のように編集する
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
document.getElementById("sendButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var li = document.createElement("li");
document.getElementById("messagesList").appendChild(li);
li.textContent = `${user} says ${message}`;
});
connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
var roomName = document.getElementById("roomName").value;
connection.invoke("AddToGroup", roomName).catch(function (err) {
return console.error(err.toString());
});
}).catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var roomName = document.getElementById("roomName").value;
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessageToGroup", roomName, user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
ちょっと説明
chat.js
は公式チュートリアルのコードを参考にしています。- 公式チュートリアルに追加したのは以下の2点です。
- SignalR接続開始時にグループに参加する処理を追加しています。
- 送信ボタンを押したときにグループを指定してチャットメッセージ送信するようにしています。
アプリを実行する
- 「F5」キーを押して実行します。
- ブラウザが起動します。
- トップページでルーム名を入力するとルーム専用のチャットページに遷移します。
- 下の画像のようにルームに分かれてメッセージをリアルタイムで送受信できます。
- 上2つのブラウザウィンドウはRoom1に、下2つはRoom2に参加している、という想定です。
まとめと感想
公式チュートリアルのSignalRサンプルアプリにルーム別メッセージ送受信機能を追加しました。そのためにSignalRのグループの仕組みを使って実装しました。
実装して感じたのはSignalRは高水準ライブラリだということ。クライアントとサーバで情報を送信しあうというより、お互いの関数を実行させる、というイメージでプログラミングできました。
ネットワーク上の流れを意識せずにプログラムの関数レベルでプログラミングできるのは処理内容に集中できるので使いやすいですね。