ASP.NET MVCでサーバーとブラウザ間での双方向通信を実現する SignalR を試してみました。
#SignalRとは
こちらのページにSignalRについて書かれているのでご一読ください。
http://www.atmarkit.co.jp/ait/articles/1303/19/news099_2.html
サーバーやブラウザ側がWebSocketに対応していればWebSocketを使うのですが、そうでなければロングポーリング(いわゆるComet?)等、他の方法での双方向通信に自動的に切り替えてくれるライブラリだそうです。
#NuGetする
VisualStudio上のNuGetでインストールします。以下の2つをインストールします。
"SignalR"で検索すると色々出てきますが、上記の "Microsoft.AspNet.SignalR" をインストールすると、その他必要なものが入ります。ですが、"Microsoft.AspNet.SignalR.Client" が入りませんでしたので、別途インストールしました。
#サンプルコードとしてプロブレスバー表示をやってみる。
ここではSignalRを利用したプログレスバー表示を実装してみます。サーバー側で時間のかかる処理をするときに、その進捗率をブラウザ側に表示することがSignalRで実装できます。
##View側のコード
まずView側のコードを書きます。編集するファイルは_Layout.cshtmlと/View/Home/Index.cshtmlの2つです。
###_Layout.cshtml
SignalR のJavaScriptファイルをインクルードします。VisualStudioでASP.NET MVCのソリューションを作り、自動生成された_Layout.cshtmlに以下のようにコードを追加します。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - マイ ASP.NET アプリケーション</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
~~ 中略 ~~
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - マイ ASP.NET アプリケーション</p>
</footer>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
<!-- 以下の2行を追加 -->
<script src="~/Scripts/jquery.signalR-2.2.1.js"></script>
<script src="~/signalR/hubs"></script>
</body>
</html>
「」というコメント行配下の2行を追加したのみで、それ以外は変更していません。追加する位置は、jQueryのインクルードの後(この場合は "@Scripts.Render("~/bundles/jquery")" の後)にしないと正常に動かないので注意してください。
あと、「~/signalR/hubs」というフォルダは存在しないのですが、上記のように追加は必要なようです。どうもSignalRが自動生成するフォルダのようです。
###Index.cshtml
次に、/Views/Home/Index.cshtmlにプログレスバーを表示させるためのコードを書きます。プログレスバーはbootstrapで描画します。ちょっと長いですが、以下にコードを示します。
@{
ViewBag.Title = "Home Page";
}
<!-- 自動生成されたサンプルのHTMLは、すべてコメントアウト
<div class="jumbotron">
<h1>ASP.NET</h1>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more »</a></p>
</div>
<div class="row">
<div class="col-md-4">
<h2>Getting started</h2>
<p>
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that
enables a clean separation of concerns and gives you full control over markup
for enjoyable, agile development.
</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more »</a></p>
</div>
<div class="col-md-4">
<h2>Get more libraries</h2>
<p>NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301866">Learn more »</a></p>
</div>
<div class="col-md-4">
<h2>Web Hosting</h2>
<p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
<p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more »</a></p>
</div>
</div>
-->
<!-- 以下のコードを追加 -->
<br />
<div class="row">
<div class="col-md-2">
<input type="button" onclick="StartInvoicing()" value="SignalRテスト" />
</div>
<div class="col-md-7">
<div class="progress">
<div class="progress-bar" id="progressBar" role="progressbar" style="width: 0%;">
</div>
</div>
</div>
<div class="col-md-1">
<label id="progressNum">進捗率</label>
</div>
</div>
<div>
<label id="message"></label>
</div>
<script type="text/javascript">
function StartInvoicing()
{
var progressNotifier = $.connection.progress;
// サーバー側からのメッセージを受信する部分
progressNotifier.client.sendMessage = function (message, count) {
UpdateProgress(message, count);
};
// サーバー側にメッセージを送る部分
$.connection.hub.start().done(function () {
progressNotifier.server.getCountAndMessage("Hello");
});
}
function UpdateProgress(message, count) {
$("#progressNum").html(count + '%');
$("#progressBar").css({ 'width': count + '%' });
$("#message").html(message);
}
</script>
VisualStudioにより自動生成されたサンプルHTMLはすべて削除します(上記の例ではコメントアウトにしています)。「」というコメントの下すべてが、今回追加したコードです。
#サーバー側のコード
まず、Startup.csファイルを編集します。
namespace WebApplication1
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR(); // ← この行を追加
}
}
}
次に、プロジェクトに任意の名前のフォルダを作成し、そこに1つファイルを作ります。作ったフォルダ内に、SignalRのハブクラスのを継承した*.csファイルを作成します。下図に示すように「新しい項目の追加」で「SignalRのハブクラス」が選べるようになっているので、それで作ると良いと思います。
SignalRハブクラスのプログラムですが、今回はプログレスバーのサンプルプログラムということで、以下のようなプログラムを書きました。
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System.Threading.Tasks;
using System.Threading;
namespace WebApplication1
{
[HubName("progress")]
public class ProgressHub : Hub
{
public int count = 1;
public static void SendMessage(string msg, int count)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
hubContext.Clients.All.sendMessage(string.Format(msg), count);
}
public void GetCountAndMessage(string msg)
{
ProgressStart();
Clients.Caller.sendMessage(string.Format(msg), count);
}
private void ProgressStart()
{
Task.Run(() => {
for (int i=1; i<=100; i++)
{
Thread.Sleep(500);
SendMessage("処理中...", i);
}
});
}
}
}
以上でサンプルプログラムはできました。これでVisualStudioからデバッグ実行でプログラムを開始すると、以下のような画面が表示されると思います。
画面中の "SignalRテスト" ボタンをクリックすると、サーバー側のプログラムが動き出し、その結果がプログレスバーに反映されます。
#動作の説明
ブラウザとサーバー間の、お互いのメソッドの呼ばれ方を以下の図に示しました。図中にも書きましたが、ブラウザからサーバーへの呼び出しメソッド、サーバーからブラウザへの呼び出しメソッドともに、引数の数は自由に増減できます。