shuhelohelo’s blog

Xamarin.Forms多めです.

.NET Conf 2019のSignalRのデモを動かす

www.youtube.com

こちらの動画を見ながら,1つ目のデモと同じリアルタイムチャットアプリを作る.

ソースコードは公開されていないが,画面を凝視すればコードが読めるし,説明は丁寧でわかりやすい.

環境

プロジェクトの作成

ASP.NET Core WebApplicationのプロジェクトを,Emptyなテンプレートを使ってをdotnet core 3.0で作成する.

Starup.csでSignalRを使用する設定を行う

ある機能をASP.NET Coreで有効にする場合は,おなじみのStartup.ConfigureServicesメソッドで使うサービスを登録し,Startup.Configureメソッドで使用することを明記する.

SignalRの場合,以下のようにservices.AddSignalR()をConfigureServicesに加え,Configureメソッド内のUserEndpoints内でendpoints.MapHub<Chat>("/chat");とルーティングを設定する.

...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();//クライアントのページも用意するので、今回は必要。
            services.AddSignalR();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();//クライアントサイドのJavaScriptライブラリにアクセスするために必要.
            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }

サーバーサイドの実装

上でChatHubクラスを実装していないのでそれを作る.

ここでMapHubクラスにわたすクラスはHubクラスを継承したもので,文字通り通信のハブとなる役割を担うものである.

以下のようなHubを作成する.

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace SignalRCorePractice
{
    internal class ChatHub:Hub
    {
        public Task Send(string message)
        {
            return Clients.All.SendAsync("Receive",message); //クライアント側のreceiveメソッドを呼ぶ.
            //クライアント側のconnection.Onメソッドで指定しているもの
        }
    }
}

このChatHubクラスにSendという名前のメソッド定義することにする. これはクライアントがメッセージを送る際に呼び出すメソッド. このメソッドは,Clients.All.SendAsync("Receive",message);とあるように,全てのクライアントに対して送られてきたデータ(message)を全てのクライアントに対して送信する.

ネットワーク越しにメソッドを呼び出すとかRPCのようだ.

サーバーサイドはこれでおしまい.

クライアントサイドの実装

クライアント側SignalRライブラリのインストール

クライアントサイドではJavaScriptでサーバーとSignalR通信を行う.

そのため,JavaScriptのSignalRライブラリが必要なのでインストールする.

@microsoft/signalrというnpmパッケージをインストールする.npmパッケージのインストールについては以下の記事を参考にする.

shuhelohelo.hatenablog.com

@microsoft/signalr 3.0.0をインストールする. f:id:shuhelohelo:20191006153728p:plain

(余談:@aspnet/signalrは古いパッケージ)

signalrのインストール後に以下のようなメッセージが表示されるかもしれないけれど,これはTypeScriptを使う場合のサジェストなので,今回は無視しておく.JavaScriptを使うので.

f:id:shuhelohelo:20191006154023p:plain

現時点でソリューションのフォルダ構成はこんな状態.

f:id:shuhelohelo:20191006154224p:plain

チャットページの実装

シンプルな作りにするために,wwwroot直下にindex.htmlを作成する.

index.htmlについての説明はコメントして書き込んだ.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <!-- 入力欄とボタン -->
    <input type="text" id="message" />
    <input type="button" value="Send" id="send" />

    <!-- ここにメッセージが追加されていく -->
    <ul id="messages"></ul>

    <!-- クライアント側のSignalRライブラリを読み込む -->
    <script src="lib/@microsoft/signalr/dist/browser/signalr.js"></script>

    <!-- サーバーにメッセージを送る処理 -->
    <script type="text/javascript">
        (async function () {

            var connection = new signalR.HubConnectionBuilder()
                .withUrl('/chathub')
                .build();

            //サーバーからのメッセージ受信時の処理
            connection.on('receive', function (message) { //サーバー側から呼び出されるメソッド名を文字列で
                //messageにサーバーからのデータが入ってくる
                //li要素を新規作成
                var li = document.createElement('li');

                //li要素のテキストとしてmessageをセットして追加
                li.innerText = message;
                document.getElementById('messages').appendChild(li);
            });

            //Sendボタンが押されたときの処理.Clickイベントにイベントハンドラを設定している.
            document.getElementById('send').addEventListener('click', async function () {
                //テキストボックス内の文字列を取得
                var value = document.getElementById('message').value;

                //サーバー側のSendメソッドにvalue(= message)を渡して実行する.
                //文字列でサーバー側のsendメソッドを指定する
                await connection.invoke('send', value);
            });

            await connection.start();
        })();
    </script>
</body>
</html>

これで完成.

実行

Ctrl+F5で実行する.

https://localhost:<<ポート番号>>/index.htmlにアクセスすると,以下のような味気のないシンプルなページが表示される.

f:id:shuhelohelo:20191006162434p:plain

同じページを複数開いて,片方で入力して送信したメッセージが全てのページに表示されることが確認できる.

f:id:shuhelohelo:20191006162653p:plain

注意点

HubのURLの文字列が同じであること.

//サーバー側
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<ChatHub>("/chathub");//ここと
        });

//クライアント側
            var connection = new signalR.HubConnectionBuilder()
                .withUrl('/chathub')//ここ
                .build();

SignalRで呼び出すサーバー側メソッドは文字列で指定する.

例えば,ChatHubクラス内にSendメソッドを作ったとして,クライアント側からこのメソッドにメッセージを送ったり,このメソッド返す値を受け取る場合は,このメソッド名の文字列(この例では「send」.大文字小文字は無視される)を使う.

//サーバー側
    internal class ChatHub:Hub
    {
        public Task Send(string message)
        {
            return Clients.All.SendAsync("Send", message);
        }
    }

//クライアント側
            //サーバーからのメッセージ受信時の処理
            connection.on('send', function (message) { //****ここ***
                //messageにサーバーからのデータが入ってくる
                //li要素を新規作成
                var li = document.createElement('li');

                //li要素のテキストとしてmessageをセットして追加
                li.innerText = message;
                document.getElementById('messages').appendChild(li);
            });

            //Sendボタンが押されたときの処理.Clickイベントにイベントハンドラを設定している.
            document.getElementById('send').addEventListener('click', async function () { //****ここ***
                //テキストボックス内の文字列を取得
                var value = document.getElementById('message').value;

                //サーバー側のSendメソッドにvalue(= message)を渡して実行する.
                //文字列でサーバー側のメソッドを指定する
                await connection.invoke('send', value); //****ここ***
            });

ソースコード

このソースコードGitHubで公開している.

github.com