miso_soup3 Blog

主に ASP.NET 関連について書いています。

knockout.js と SignalR でチャット作成

先日 knockout.js を使ったリストのバインディングについて書きました。
今日は、それに SignalR の要素を追加して、簡単なチャットアプリの作成について書きます。

名前とテキストの入力欄があり、投稿ボタンを押すと、
リストへ表示されます。

SignalR を使うと上の図のように、他のブラウザ(他のユーザ)側で投稿されたコメントを、
リアルタイムに反映させることができます。

このチャットもどきアプリ、
少しのHtml と JavaSciprt を書くだけで作成することができました!

まずは knockout.js だけで

リアルタイムに反映させることは今は忘れて、まずは knockout.js を使って、コメントの投稿・表示・削除を実装します。

準備

jQuery と knockout.js を nuget 等で取得し、読み込みます。

<script src="~/Scripts/jquery-1.8.2.js"></script>
<script src="~/Scripts/knockout-2.2.0.js"></script>
Html

Html は以下のようになります。

<label>
    名前をいれてください。<input type="text" data-bind="value: yourName" />
</label>

<ul data-bind="foreach: comments" class="comment-list">
    <li>
        <span data-bind="text: text" class="text"></span>
        <span data-bind="text: name" class="name text-info"></span>
        <span data-bind="click: $root.deleteComment" class="delete label">削除</span>
    </li>
</ul>
    
<input type="text" data-bind="value: newCommentText" />
<button data-bind="click: addComment" class="btn">投稿</button>
JavaScript

JavaScript はこちらです。

$(function () {

    function comment(data) {
        //コメントのモデル
        this.text = data.text;
        this.name = data.name;
    }

    function viewModel() {

        var self = this;

        //コメント
        self.comments = ko.observableArray([
                new comment({ text: "はろー", name: "ジョン" }),
                new comment({ text: "やっほー", name: "太郎" })
        ]);

        //新しく追加したいコメント
        self.newCommentText = ko.observable('');

        //操作している人の名前
        self.yourName = ko.observable('');

        //コメントを追加します
        self.addComment = function () {
            var newComment = new comment({ text: self.newCommentText(), name: self.yourName() });
            self.comments.push(newComment);
            self.newCommentText('');
        }

        //コメントを削除します
        self.deleteComment = function (comment) {
            self.comments.remove(comment);

        }
    }

    var viewModel = new viewModel();
    //ViewModelをバインドします。
    ko.applyBindings(viewModel);
});

以上で、ひとりチャットの完成です!

knockout.js を使うと、コメントの追加・削除・表示の機能がこれだけのコードで実装できるんですね・・。
リストのバインディングについては、公式のチュートリアル collections
がとてもわかりやすいです。*1

特筆するところは、1点だけ・・・ $root についてです。
HTML で書いた削除ボタンのところです。

<span data-bind="click: $root.deleteComment" class="delete label">削除</span>

削除ボタンを click したら、deleteComment 関数を呼び出すようにしています。
この $root というのは、バインド対象のモデルを指しています。(だいたいは、ko.applyBindings() の中にいれたオブジェクト)
今の場合だと、JavaScript に書いた viewModel にあたります。

なぜ、$root という指定が必要かというと、削除ボタンは、配列のバインド(ul data-bind="foreach:...)の中にあるためです。
このような指定方法には、$root の他にも $data、$parent 等いろいろあります。(Binding context

SignalR

ひとりチャットで寂しいので、SignalR を使って他ユーザともチャットできるようにします。
(更新ボタンなんてものはいらない!)

開発者が書くべき内容は、

  • 1.【クライアント側】コメントを追加した時に、追加したユーザのクライアントからサーバへ、通知する。
  • 2.【サーバ側】その時―クライアントから通知が来た時に、サーバ側から全クライアント(全ユーザ)へ、コメントが追加されたことを通知する。
  • 3.【クライアント側】サーバからコメント追加のお知らせが来たら、クライアント側で画面を反映させる。

です。

インストール

SignalR を nuget Microsoft.AspNet.SignalR/1.0.0-rc1 から取得します。

Install-Package Microsoft.AspNet.SignalR -Pre
設定

インストールすると、readme.txt が表示されます。
その中にも書いてあるように、Global.asax.cs(ASP.NET MVC の場合) に、以下のように記述します。

using Microsoft.AspNet.SignalR;

protected void Application_Start()
{
    RouteTable.Routes.MapHubs();
    //...
}
signalR-1.0.0-rc1.js ファイルの読み込み

インストールした SignalR の JavaScript ファイルを読み込みます。

<script src="~/Scripts/jquery.signalR-1.0.0-rc1.js"></script>
<script src="~/signalr/hubs"></script>

2行目は、先ほどの RouteTable.Routes.MapHubs(); と対応しています。
記述しないとエラーになります。

クライアント側の仕事

先ほど書いた JavaScipt にコードを追加します。
以下、追加したところだけ抜粋します。

ViewModel のバインドを、SignalR が接続されたときに開始するようにします。

    var viewModel = new viewModel();

    $.connection.hub.start().done(function () {
        //接続されたら

        //ViewModelをバインドします。
        ko.applyBindings(viewModel);
    });

コメントが投稿された時に、サーバ側へ送信する処理を追加します。
(★の部分です。)

//コメントを追加します
self.addComment = function () {
    var newComment = new comment({ text: self.newCommentText(), name: self.yourName() });
    self.comments.push(newComment);
    self.newCommentText('');

    //★1.コメントを追加した時に、追加したユーザのクライアントからサーバへ、通知する。
    commentHub.server.notifyAddComment(newComment.text, newComment.name);
}

notifyAddComment という名前は、サーバ側で定義したものと同じ名前です。後ほど登場します。

サーバから、コメント追加の通知が来たら、リストに反映させます。
反映は、knockout.js を利用します。

//コメントのハブを取得
var commentHub = $.connection.commentHub;

commentHub.client.addedComment = function (text, name) {
    //★3.サーバからコメント追加のお知らせが来たら、クライアント側で画面を反映させる。

    //コメントモデルを作成して
    var newComment = new comment({ text: text, name: name });

    //ViewModelのcommentsへ追加
    viewModel.comments.push(newComment);
};

addedComment という名前も、さきほどと同様サーバ側で定義した名前と同じです。

サーバ側の仕事

残りの仕事である、「2.クライアントから通知が来た時に、サーバ側から全クライアント(全ユーザ)へ、コメントが追加されたことを通知する。」
を実装します。

プロジェクトの任意の場所に、Hub クラスを追加します。
今回は、Hubs フォルダ配下に追加しました。

ASP.NET and Web Tools 2012.2 をインストールすれば、
Hub クラスのテンプレートを選択できます。

クラス名は、「CommentHub」にします。

using Microsoft.AspNet.SignalR.Hubs;

namespace SignalRChat.Hubs
{
    public class CommentHub : Hub
    {
        public void NotifyAddComment(String text, String name)
        {
            Clients.AllExcept(Context.ConnectionId).addedComment(text, name);
        }
    }
}

先ほどの JavaScipt でかいた、commentHub.server.notifyAddComment(newComment.text, newComment.name); が呼び出されると、
上の NotifyAddComment メソッドが実行されます。

Clients.AllExcept(...) というのは、指定した接続クライアント”以外”全員へ送信することを意味します。
Context.ConnectionId は、呼び出したクライアントの接続IDです。
なぜ呼び出したクライアントを除いているのかというと、コメントが重複して表示されないようにするためです。
先ほどの JavaScript 側の実装では、コメントを投稿した時に、サーバへ送信する前に自分の画面のリストへ反映させているのでこのようにしました。
(※JavaScript 側の実装を変更すれば、ここは Clients.All.addedComment(text, name);(クライアント全員へ送信する) と書けます。)

おしまい

実装は以上になります。
一つ忘れていました!削除時のリアルタイム対応は実装していません。

必要なコードは、まとめて gist にあげました。

参考サイトは、こちら:Microsoft ASP.NET SignalRです。

*1:[knockout.js のチュートリアル http://learn.knockoutjs.com は、なかなか楽しいです!