次の方法で共有


チュートリアル: SignalR 2 を使用したサーバー ブロードキャスト

警告

このドキュメントは、最新版の SignalR を対象としていません。 ASP.NET Core SignalR に関する記事を参照してください。

このチュートリアルでは、ASP.NET SignalR 2 を使用してサーバー ブロードキャスト機能を提供する Web アプリケーションの作成方法について説明します。 サーバー ブロードキャストとは、クライアントに送信される通信がサーバーで開始することを意味します。

このチュートリアルで作成するアプリケーションは、サーバー ブロードキャスト機能の一般的なシナリオである株価ティッカーをシミュレートしています。 株価のランダムな更新に加え、すべての接続済みクライアントへの更新のブロードキャストが、サーバーで定期的に行われます。 ブラウザーでは、"変動" 列と % 列の数値とシンボルが、サーバーからの通知に応答して動的に変動します。 他のブラウザーで同じ URL を開いている場合は、どのブラウザーにも、同じデータおよびそのデータが同じように変動するのが同時に表示されます。

Screenshot showing how multiple web browsers show the same updated data simultaneously.

このチュートリアルでは、次の作業を行いました。

  • プロジェクトを作成する
  • サーバー コードを設定する
  • サーバー コードを調べる
  • クライアント コードを設定する
  • クライアント コードを調べる
  • アプリケーションをテストする
  • ログの有効化

重要

アプリケーションを構築する手順を実行しない場合は、SignalR.Sample パッケージを新しい空の ASP.NET Web アプリケーション プロジェクトからインストールできます。 このチュートリアルの手順を実行せずに NuGet パッケージをインストールする場合は、readme.txt ファイルの指示に従う必要があります。 パッケージを実行するには、インストールされたパッケージに ConfigureSignalR メソッドを呼び出す OWIN スタートアップ クラスを追加する必要があります。 OWIN スタートアップ クラスを追加しないと、エラーが発生します。 この記事の「StockTicker サンプルをインストールする」セクションを参照してください。

前提条件

プロジェクトを作成する

このセクションでは、Visual Studio 2017 を使用して空の ASP.NET Web アプリケーションを作成する方法について説明します。

  1. Visual Studio で、ASP.NET Web アプリケーションを作成します。

    Screenshot showing how to create an ASP.NET Web Application.

  2. [新しい ASP.NET Web アプリケーション - SignalR.StockTicker] ウィンドウで、[空] を選択したまま [OK] を選択します。

サーバー コードを設定する

このセクションでは、サーバー上で実行されるコードを設定します。

Stock クラスを作成する

まず、株式に関する情報の格納と送信に使用する Stock モデル クラスを作成します。

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[クラス] を選択します。

  2. クラスに "Stock" という名前を付けて、プロジェクトに追加します。

  3. Stock.cs ファイル内のコードを次のコードに置き換えます。

    using System;
    
    namespace SignalR.StockTicker
    {
        public class Stock
        {
            private decimal _price;
    
            public string Symbol { get; set; }
    
            public decimal Price
            {
                get
                {
                    return _price;
                }
                set
                {
                    if (_price == value)
                    {
                        return;
                    }
    
                    _price = value;
    
                    if (DayOpen == 0)
                    {
                        DayOpen = _price;
                    }
                }
            }
    
            public decimal DayOpen { get; private set; }
    
            public decimal Change
            {
                get
                {
                    return Price - DayOpen;
                }
            }
    
            public double PercentChange
            {
                get
                {
                    return (double)Math.Round(Change / Price, 4);
                }
            }
        }
    }
    

    株式を作成するときに設定するプロパティは、Symbol (Microsoft 用の MSFT など) と Price の 2 つがあります。 その他のプロパティは、Price を設定する方法とタイミングによって異なります。 Price を初めて設定したときに、値が DayOpen に反映されます。 その後、Price を設定すると、アプリによって Change プロパティ値と PercentChange プロパティ値が、PriceDayOpen の差に基づいて計算されます。

StockTickerHub クラスと StockTicker クラスを作成する

SignalR Hub API を使用して、サーバー対クライアントの対話を処理します。 SignalR Hub クラスから派生した StockTickerHub クラスを使用して、クライアントからの接続とメソッド呼び出しを処理します。 また、在庫データを更新し、Timer オブジェクトを実行する必要もあります。 Timer オブジェクトを使用すると、クライアント接続とは関係なく、株価の更新が定期的にトリガーされます。 Hub は一時的であるため、これらの関数を Hub クラスに配置することはできません。 アプリでは、クライアントからサーバーへの接続や呼び出しのように、ハブ上に各タスクの Hub クラス インスタンスが作成されます。 そのため、株式データの維持、株価の更新、株価の更新のブロードキャストを行うメカニズムを別のクラスで実行する必要があります。 クラスに StockTicker という名前を付けます。

Broadcasting from StockTicker

サーバー上での実行に必要な StockTicker クラスのインスタンスは 1 つのみです。そのため、各 StockTickerHub インスタンスからシングルトン StockTicker インスタンスへの参照を設定する必要があります。 StockTicker クラスでは、株式データが存在しており、更新のトリガーが行われますが、StockTickerHub クラスではないため、クライアントへブロードキャストする必要があります。 StockTicker クラスでは、SignalR Hub 接続コンテキスト オブジェクトへの参照を取得する必要があります。 その後、SignalR 接続コンテキスト オブジェクトを使用して、クライアントへブロードキャストします。

StockTickerHub.cs を作成する

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[新しい項目] を選択します。

  2. [新しい項目の追加 - SignalR.StockTicker] で、[インストール済み]>[Visual C#]>[Web]>[SignalR] を選択して、[SignalR Hub クラス (v2)] を選択します。

  3. クラスに "StockTickerHub" という名前を付けて、プロジェクトに追加します。

    この手順では、StockTickerHub.cs クラス ファイルを作成します。 同時に、SignalR をサポートするスクリプト ファイルとアセンブリ参照のセットをプロジェクトに追加します。

  4. StockTickerHub.cs ファイル内のコードを次のコードに置き換えます。

    using System.Collections.Generic;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        [HubName("stockTickerMini")]
        public class StockTickerHub : Hub
        {
            private readonly StockTicker _stockTicker;
    
            public StockTickerHub() : this(StockTicker.Instance) { }
    
            public StockTickerHub(StockTicker stockTicker)
            {
                _stockTicker = stockTicker;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stockTicker.GetAllStocks();
            }
        }
    }
    
  5. ファイルを保存します。

アプリでは、Hub クラスを使用して、サーバー上でのクライアントによる呼び出しを可能にするメソッドを定義します。 GetAllStocks() という 1 つのメソッドを定義します。 クライアントでは、サーバーへの最初の接続時に、このメソッドが呼び出され、すべての株式とそれらの現在の株価の一覧の取得が行われます。 このメソッドを使用すると、データがメモリから返されるため、IEnumerable<Stock> を同時に実行して返すことができます。

このメソッドで、データベース参照や Web サービス呼び出しなど、待機を伴う処理を行ってデータを取得する必要がある場合は、Task<IEnumerable<Stock>> を戻り値として指定して、非同期処理を有効にします。 詳細については、「ASP.NET SignalR Hubs API ガイド - サーバー (C#)」の「非同期的に実行するタイミング」を参照してください。

HubName 属性を使用すると、クライアント上にある JavaScript コードの Hub をアプリで参照する方法が指定されます。 この属性を使用しない場合のクライアントの既定の名前は、camelCase バージョンのクラス名です。この場合は stockTickerHub になります。

後で StockTicker クラスを作成するときに確認しますが、このアプリでは、そのクラスのシングルトン インスタンスが、その静的 Instance プロパティに作成されます。 接続または切断されたクライアントに数にかかわらず、StockTicker のそのシングルトン インスタンスはメモリ内にあります。 そのインスタンスは、現在の株式情報を返すために GetAllStocks() メソッドで使用するものです。

Create StockTicker.cs

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[クラス] を選択します。

  2. クラスに "StockTicker" という名前を付けて、プロジェクトに追加します。

  3. StockTicker.cs ファイル内のコードを次のコードに置き換えます。

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        public class StockTicker
        {
            // Singleton instance
            private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
            private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
            private readonly object _updateStockPricesLock = new object();
    
            //stock can go up or down by a percentage of this factor on each change
            private readonly double _rangePercent = .002;
    
            private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250);
            private readonly Random _updateOrNotRandom = new Random();
    
            private readonly Timer _timer;
            private volatile bool _updatingStockPrices = false;
    
            private StockTicker(IHubConnectionContext<dynamic> clients)
            {
                Clients = clients;
    
                _stocks.Clear();
                var stocks = new List<Stock>
                {
                    new Stock { Symbol = "MSFT", Price = 30.31m },
                    new Stock { Symbol = "APPL", Price = 578.18m },
                    new Stock { Symbol = "GOOG", Price = 570.30m }
                };
                stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));
    
                _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    
            }
    
            public static StockTicker Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
    
            private IHubConnectionContext<dynamic> Clients
            {
                get;
                set;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stocks.Values;
            }
    
            private void UpdateStockPrices(object state)
            {
                lock (_updateStockPricesLock)
                {
                    if (!_updatingStockPrices)
                    {
                        _updatingStockPrices = true;
    
                        foreach (var stock in _stocks.Values)
                        {
                            if (TryUpdateStockPrice(stock))
                            {
                                BroadcastStockPrice(stock);
                            }
                        }
    
                        _updatingStockPrices = false;
                    }
                }
            }
    
            private bool TryUpdateStockPrice(Stock stock)
            {
                // Randomly choose whether to update this stock or not
                var r = _updateOrNotRandom.NextDouble();
                if (r > .1)
                {
                    return false;
                }
    
                // Update the stock price by a random factor of the range percent
                var random = new Random((int)Math.Floor(stock.Price));
                var percentChange = random.NextDouble() * _rangePercent;
                var pos = random.NextDouble() > .51;
                var change = Math.Round(stock.Price * (decimal)percentChange, 2);
                change = pos ? change : -change;
    
                stock.Price += change;
                return true;
            }
    
            private void BroadcastStockPrice(Stock stock)
            {
                Clients.All.updateStockPrice(stock);
            }
    
        }
    }
    

すべてのスレッドで StockTicker コードの同じインスタンスが実行されるため、StockTicker クラスはスレッドセーフである必要があります。

サーバー コードを調べる

サーバー コードを調べると、アプリのしくみを理解しやすくなります。

静的フィールドへのシングルトン インスタンスの格納

このコードを使用すると、Instance プロパティをクラスのインスタンスでバックする静的 _instance フィールドが初期化されます。 コンストラクターはプライベートであるため、アプリで作成できるクラスのインスタンスはこれ 1 つのみです。 アプリでは、_instance フィールドに遅延初期化を使用します。 これは、パフォーマンス上の理由からではありません。 インスタンスの作成がスレッドセーフであることを確認するためです。

private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));

public static StockTicker Instance
{
    get
    {
        return _instance.Value;
    }
}

クライアントがサーバーに接続されるたびに、StockTickerHub クラスで前述したように、別のスレッドで実行されている StockTickerHub クラスの新しいインスタンスによって、StockTicker.Instance 静的プロパティから StockTicker シングルトン インスタンスの取得が行われます。

ConcurrentDictionary への株式データの格納

コンストラクターによって、同じ株式データで _stocks コレクションが初期化されるとともに、GetAllStocks によって株式が返されます。 前述のように、この株式のコレクションは、クライアントが呼び出すことができる Hub クラスのサーバー メソッドである StockTickerHub.GetAllStocks によって返されます。

private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
private StockTicker(IHubConnectionContext<dynamic> clients)
{
    Clients = clients;

    _stocks.Clear();
    var stocks = new List<Stock>
    {
        new Stock { Symbol = "MSFT", Price = 30.31m },
        new Stock { Symbol = "APPL", Price = 578.18m },
        new Stock { Symbol = "GOOG", Price = 570.30m }
    };
    stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));

    _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
}

public IEnumerable<Stock> GetAllStocks()
{
    return _stocks.Values;
}

株式コレクションは、スレッド セーフの ConcurrentDictionary 型として定義されます。 別の方法としては、Dictionary オブジェクトを使用して、辞書に変更を加える際に辞書を明示的にロックすることもできます。

このサンプル アプリケーションでは、アプリケーション データをメモリに格納しても、アプリで StockTicker インスタンスが破棄されたときにそのデータが失われても、かまいません。 実際のアプリケーションでは、データベースのようなバックエンド データ ストアを操作します。

株価の定期的な更新

コンストラクターを使用すると、株価をランダムに更新するメソッドを定期的に呼び出す Timer オブジェクトが起動します。

_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);

private void UpdateStockPrices(object state)
{
    lock (_updateStockPricesLock)
    {
        if (!_updatingStockPrices)
        {
            _updatingStockPrices = true;

            foreach (var stock in _stocks.Values)
            {
                if (TryUpdateStockPrice(stock))
                {
                    BroadcastStockPrice(stock);
                }
            }

            _updatingStockPrices = false;
        }
    }
}

private bool TryUpdateStockPrice(Stock stock)
{
    // Randomly choose whether to update this stock or not
    var r = _updateOrNotRandom.NextDouble();
    if (r > .1)
    {
        return false;
    }

    // Update the stock price by a random factor of the range percent
    var random = new Random((int)Math.Floor(stock.Price));
    var percentChange = random.NextDouble() * _rangePercent;
    var pos = random.NextDouble() > .51;
    var change = Math.Round(stock.Price * (decimal)percentChange, 2);
    change = pos ? change : -change;

    stock.Price += change;
    return true;
}

Timer によって UpdateStockPrices が呼び出されます。これにより、state パラメータに null が渡されます。 株価の更新前に、アプリでは、_updateStockPricesLock オブジェクトがロックされます。 このコードを使用すると、別のスレッドで株価が既に更新されているかどうかの確認が行われるとともに、一覧の各株式に対して TryUpdateStockPrice が呼び出されます。 TryUpdateStockPrice メソッドでは、株価を変更するかどうか、およびどのくらい変更するかが決定されます。 株価が変動する場合、アプリでは、BroadcastStockPrice が呼び出され、株価の変動がすべての接続済みクライアントにブロードキャストされます。

_updatingStockPrices フラグは、確実にスレッドセーフであるように、volatile と指定されています。

private volatile bool _updatingStockPrices = false;

実際のアプリケーションでは、TryUpdateStockPrice メソッドを使用して Web サービスを呼び出し、株価を検索します。 このコードでは、アプリで乱数ジェネレーターを使用して、ランダムに変更を行います。

StockTicker クラスを使用してクライアントにブロードキャストできるように SignalR コンテキストを取得する

株価の変更はこの StockTicker オブジェクトを起因とするため、すべての接続済みクライアントで updateStockPrice メソッドを呼び出す必要があるのはこのオブジェクトとなります。 Hub クラスには、クライアント メソッドを呼び出すための API がありますが、StockTickerHub クラスから派生しておらず、どの Hub オブジェクトへの参照もありません。 接続済みクライアントへブロードキャストするには、StockTicker クラスで、StockTickerHub クラスの SignalR コンテキスト インスタンスを取得し、それを使用してクライアントに対してメソッドを呼び出す必要があります。

このコードを使用すると、シングルトン クラス インスタンスが作成されたとき、その参照がコンストラクターに渡されたとき、コンストラクターがそれを Clients プロパティに配置したときに、SignalR コンテキストへの参照を取得できます。

コンテキストの取得が 1 回のみでよい理由は 2 つあります。コンテキストの取得がコストの高いタスクであるという理由と、1 回取得すればクライアントに送信されたメッセージが意図された順序で確実に保持されるという理由です。

private readonly static Lazy<StockTicker> _instance =
    new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));

private StockTicker(IHubConnectionContext<dynamic> clients)
{
    Clients = clients;

    // Remainder of constructor ...
}

private IHubConnectionContext<dynamic> Clients
{
    get;
    set;
}

private void BroadcastStockPrice(Stock stock)
{
    Clients.All.updateStockPrice(stock);
}

コンテキストの Clients プロパティを取得してそれを StockTickerClient プロパティに配置すると、Hub クラスにあるときと同じように見えるクライアント メソッドを呼び出すコードを記述できます。 たとえば、すべてのクライアントにブロードキャストするには、Clients.All.updateStockPrice(stock) を記述できます。

BroadcastStockPrice で呼び出している updateStockPrice メソッドはまだ存在していません。 後で、クライアントで実行されるコードを記述するときに追加します。 Clients.All は動的であるため、ここでは updateStockPrice を参照できます。つまり、実行時にアプリによって式が評価されます。 このメソッド呼び出しが実行されると、SignalR によってメソッド名とパラメータ値がクライアントに送信され、クライアントに updateStockPrice という名前のメソッドがある場合は、アプリによってそのメソッドが呼び出され、そこにパラメータ値が渡されます。

Clients.All は、すべてのクライアントへの送信を意味します。 SignalR には、送信先のクライアントまたはクライアント グループを指定するためのオプションが他にも用意されています。 詳細については、HubConnectionContext に関する記事を参照してください。

SignalR ルートを登録する

サーバーには、どの URL をインターセプトして SignalR に転送するかを判断する機能が必要です。 そのために、OWIN スタートアップ クラスを追加します。

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[新しい項目] を選択します。

  2. [新しい項目の追加 - SignalR.StockTicker] で、[インストール済み]>[Visual C#]>[Web] を選択して、[OWIN スタートアップ クラス] を選択します。

  3. クラスに "Startup" という名前を付けて、[OK] を選択します。

  4. Startup.cs ファイル内の既定のコードを次のコードに置き換えます。

    using System;
    using System.Threading.Tasks;
    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(SignalR.StockTicker.Startup))]
    
    namespace SignalR.StockTicker
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // Any connection or hub wire up and configuration should go here
                app.MapSignalR();
            }
    
        }
    }
    

これで、サーバー コードの設定が完了しました。 次のセクションでは、クライアントを設定します。

クライアント コードを設定する

このセクションでは、クライアント上で実行されるコードを設定します。

HTML ページと JavaScript ファイルを作成する

HTML ページでデータを表示し、JavaScript ファイルでデータを整理します。

StockTicker.html を作成する

最初に、HTML クライアントを追加します。

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[HTML ページ] を選択します。

  2. ファイルに "StockTicker" という名前を付けて、[OK] を選択します。

  3. StockTicker.html ファイル内の既定のコードを次のコードに置き換えます。

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>ASP.NET SignalR Stock Ticker</title>
        <style>
            body {
                font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
                font-size: 16px;
            }
            #stockTable table {
                border-collapse: collapse;
            }
                #stockTable table th, #stockTable table td {
                    padding: 2px 6px;
                }
                #stockTable table td {
                    text-align: right;
                }
            #stockTable .loading td {
                text-align: left;
            }
        </style>
    </head>
    <body>
        <h1>ASP.NET SignalR Stock Ticker Sample</h1>
    
        <h2>Live Stock Table</h2>
        <div id="stockTable">
            <table border="1">
                <thead>
                    <tr><th>Symbol</th><th>Price</th><th>Open</th><th>Change</th><th>%</th></tr>
                </thead>
                <tbody>
                    <tr class="loading"><td colspan="5">loading...</td></tr>
                </tbody>
            </table>
        </div>
    
        <!--Script references. -->
        <!--Reference the jQuery library. -->
        <script src="/Scripts/jquery-1.10.2.min.js" ></script>
        <!--Reference the SignalR library. -->
        <script src="/Scripts/jquery.signalR-2.1.0.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <!--Reference the StockTicker script. -->
        <script src="StockTicker.js"></script>
    </body>
    </html>
    

    HTML では、5 つの列、ヘッダー行、1 つのセルが 5 列すべてにまたがっているデータ行から成る表が作成されます。 アプリが起動すると、データ行に "読み込んでいます..." と短時間表示されます。 JavaScript コードを使用すると、その行は削除され、その場所には、サーバーから取得した株式データが含まれている行が追加されます。

    スクリプト タグでは、次を指定します。

    • jQuery スクリプト ファイル。

    • SignalR コア スクリプト ファイル。

    • SignalR プロキシ スクリプト ファイル。

    • 後で作成する StockTicker スクリプト ファイル。

    アプリによって、SignalR プロキシ スクリプト ファイルが動的に生成されます。 そこでは、"/signalr/hubs" URL が指定されるとともに、Hub クラスのメソッドに対するプロキシ メソッド (この場合は StockTickerHub.GetAllStocks) が定義されます。 または、SignalR ユーティリティを使用すると、この JavaScript ファイルを手動で生成できます。 MapHubs メソッドの呼び出しでは、動的なファイル作成を必ず無効にしてください。

  4. ソリューション エクスプローラーで、[スクリプト] を展開します。

    jQuery と SignalR のスクリプト ライブラリがプロジェクトに表示されます。

    重要

    パッケージ マネージャーを使用すると、SignalR スクリプトの新しいバージョンをインストールできます。

  5. コード ブロック内のスクリプト参照を、プロジェクト内のスクリプト ファイルのバージョンに対応するように更新します。

  6. ソリューション エクスプローラーで、StockTicker.html を右クリックして、[スタート ページとして設定] を選択します。

StockTicker.js を作成する

次に、JavaScript ファイルを作成します。

  1. ソリューション エクスプローラーで、プロジェクトを右クリックして、[追加]>[JavaScript ファイル] を選択します。

  2. ファイルに "StockTicker" という名前を付けて、[OK] を選択します。

  3. StockTicker.js ファイルに次のコードを追加します。

    // A simple templating method for replacing placeholders enclosed in curly braces.
    if (!String.prototype.supplant) {
        String.prototype.supplant = function (o) {
            return this.replace(/{([^{}]*)}/g,
                function (a, b) {
                    var r = o[b];
                    return typeof r === 'string' || typeof r === 'number' ? r : a;
                }
            );
        };
    }
    
    $(function () {
    
        var ticker = $.connection.stockTickerMini, // the generated client-side hub proxy
            up = '▲',
            down = '▼',
            $stockTable = $('#stockTable'),
            $stockTableBody = $stockTable.find('tbody'),
            rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{DayOpen}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>';
    
        function formatStock(stock) {
            return $.extend(stock, {
                Price: stock.Price.toFixed(2),
                PercentChange: (stock.PercentChange * 100).toFixed(2) + '%',
                Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down
            });
        }
    
        function init() {
            ticker.server.getAllStocks().done(function (stocks) {
                $stockTableBody.empty();
                $.each(stocks, function () {
                    var stock = formatStock(this);
                    $stockTableBody.append(rowTemplate.supplant(stock));
                });
            });
        }
    
        // Add a client-side hub method that the server will call
        ticker.client.updateStockPrice = function (stock) {
            var displayStock = formatStock(stock),
                $row = $(rowTemplate.supplant(displayStock));
    
            $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
                .replaceWith($row);
            }
    
        // Start the connection
        $.connection.hub.start().done(init);
    
    });
    

クライアント コードを調べる

クライアント コードを調べると、アプリを動作させるためにクライアント コードとサーバー コードとの間でどのような対話が行われているかを理解しやすくなります。

接続の開始

$.connection は SignalR プロキシを参照します。 このコードを使用すると、StockTickerHub クラスのプロキシへの参照を取得して、ticker 変数に格納できます。 プロキシ名は、HubName 属性によって設定された名前です。

var ticker = $.connection.stockTickerMini
[HubName("stockTickerMini")]
public class StockTickerHub : Hub

すべての変数と関数を定義した後、ファイル内のコードの最終行では、SignalR start 関数の呼び出しによって SignalR 接続が初期化されます。 start 関数は、非同期的に実行され、jQuery Deferred オブジェクトを返します。 done 関数を呼び出すと、アプリで非同期アクションが完了したときに呼び出す関数を指定できます。

$.connection.hub.start().done(init);

すべての株式の取得

init 関数を使用すると、サーバー上で getAllStocks 関数が呼び出され、サーバーから返される情報を使用して株価一覧が更新されます。 サーバー上でメソッド名がパスカル ケースになっている場合でも、クライアントでは既定で camelCasing を使用する必要があることに注意してください。 camelCasing ルールは、オブジェクトにではなく、メソッドにのみ適用されます。 たとえば、参照先は、stock.symbolstock.price であり、stock.Symbolstock.Price ではありません。

function init() {
    ticker.server.getAllStocks().done(function (stocks) {
        $stockTableBody.empty();
        $.each(stocks, function () {
            var stock = formatStock(this);
            $stockTableBody.append(rowTemplate.supplant(stock));
        });
    });
}
public IEnumerable<Stock> GetAllStocks()
{
    return _stockTicker.GetAllStocks();
}

init メソッドでは、stock オブジェクトのプロパティを書式設定する formatStock を呼び出してから rowTemplate 変数のプレースホルダーを stock オブジェクトのプロパティ値に置き換える supplant を呼び出すと、サーバーから受信した株式オブジェクトごとにテーブル行の HTML がアプリによって作成されます。 その後、結果として作成された HTML が、株価一覧に追加されます。

Note

init は、非同期 start 関数の実行後に実行する callback 関数として渡すことで呼び出します。 start を呼び出した後に init を別の JavaScript ステートメントとして呼び出した場合、この関数は、start 関数による接続の確立が完了するのを待たずにすぐ実行されるため、失敗します。 その場合、init 関数は、アプリによってサーバー接続が確立される前に、getAllStocks 関数を呼び出そうとします。

更新された株価の取得

サーバーでは、株価が変更されると、接続済みクライアント上で updateStockPrice が呼び出されます。 このアプリでは、この関数が stockTicker プロキシのクライアント プロパティに追加され、サーバーからの呼び出しに使用できるようになります。

ticker.client.updateStockPrice = function (stock) {
    var displayStock = formatStock(stock),
        $row = $(rowTemplate.supplant(displayStock));

    $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
        .replaceWith($row);
    }

updateStockPrice 関数を使用すると、init 関数と同じ方法で、サーバーから受信した株式オブジェクトがテーブル行に書式設定されます。 表に行を追加するのではなく、株式の現在の行を表内で検索し、その行を新しい行に置き換えます。

アプリケーションをテストする

アプリをテストして、動作していることを確認できます。 すべてのブラウザー ウィンドウに、ライブ株価一覧が表示され、そこには株価の変動も表示されます。

  1. ツールバーの [スクリプト デバッグ] をオンにして、再生ボタンを選択し、アプリをデバッグ モードで実行します。

    Screenshot of user turning on debugging mode and selecting play.

    ブラウザー ウィンドウが開き、ライブ株価一覧が表示されます。 株価一覧には、"読み込んでいます..." の文章が最初に表示されます。その後、しばらくすると、アプリに初期株式データが表示され、株価の変動が始まります。

  2. ブラウザーから URL をコピーして、ブラウザーを 2 つ新たに開き、URL をアドレス バーに貼り付けます。

    株式の初期表示は 1 つめのブラウザーと同じであり、変動が同時に起こります。

  3. すべてのブラウザーを閉じて、新しいブラウザーを開き、同じ URL に移動します。

    StockTicker シングルトン オブジェクトは、サーバーで引き続き実行されます。 ライブ株価一覧は、株価が変動し続けていたことを示しています。 数値の変動がゼロになっている初期一覧表は表示されません。

  4. ブラウザーを閉じます。

ログの有効化

SignalR には、トラブルシューティングを支援するためにクライアント上で有効にできるログ記録機能が組み込まれています。 このセクションでは、ログ記録を有効にして、SignalR で次のトランスポート方法のうちどれが使用されているのかをログから判断する方法を示す例を確認します。

どのような接続の場合も、SignalR では、サーバーとクライアントの両方でサポートされる最適なトランスポート方法を選択します。

  1. StockTicker.js を開きます。

  2. この強調表示されたコード行を追加し、ファイルの末尾で接続を初期化するコードの直前でログ記録を有効にします。

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  3. F5 キーを押してプロジェクトを実行します。

  4. ブラウザーの開発者ツール ウィンドウを開き、コンソールを選択してログを表示します。 新しい接続のトランスポート方法をネゴシエートする SignalR のログを表示するには、ページの更新が必要となる場合があります。

    • Windows 8 (IIS 8) で Internet Explorer 10 を実行している場合、トランスポート方法は WebSockets です。

    • Windows 7 (IIS 7.5) で Internet Explorer 10 を実行している場合、トランスポート方法は iframe です。

    • Windows 8 (IIS 8) で Firefox 19 を実行している場合、トランスポート方法は WebSockets です。

      ヒント

      Firefox では、Firebug アドインをインストールして、コンソール ウィンドウを取得します。

    • Windows 7 (IIS 7.5) で Firefox 19 を実行している場合、トランスポート方法はサーバー送信イベントです。

StockTicker サンプルをインストールする

Microsoft.AspNet.SignalR.Sample を使用すると、StockTicker アプリケーションをインストールできます。 NuGet パッケージには、一から作成した簡略化されたバージョンよりも、多くの機能が含まれています。 チュートリアルのこのセクションでは、NuGet パッケージをインストールして、新しい各機能とそれらの機能を実装するコードを確認します。

重要

このチュートリアルの前の手順を実行せずにパッケージをインストールする場合は、OWIN スタートアップ クラスをプロジェクトに追加する必要があります。 NuGet パッケージでは、この readme.txt ファイルで、この手順を説明しています。

SignalR.Sample NuGet パッケージをインストールする

  1. ソリューション エクスプローラーで、プロジェクトを右クリックし、 [NuGet パッケージの管理] を選択します。

  2. [NuGet Package manager: SignalR.StockTicker] で、 [参照] を選択します。

  3. [パッケージ ソース] から、[nuget.org] を選択します。

  4. 検索ボックスに「SignalR.Sample」と入力して、[Microsoft.AspNet.SignalR.Sample]>[インストール] を選択します。

  5. ソリューション エクスプローラーで、SignalR.Sample フォルダーを展開します。

    SignalR.Sample パッケージをインストールしたら、フォルダーとその内容が作成されました。

  6. SignalR.Sample フォルダーで、StockTicker.html を右クリックして、[スタート ページとして設定] を選択します。

    Note

    SignalR.Sample NuGet パッケージをインストールすると、Scripts フォルダーにある jQuery のバージョンが変更される可能性があります。 パッケージによって SignalR.Sample フォルダーにインストールされた新しい StockTicker.html ファイルは、パッケージによってインストールされた jQuery バージョンと同期されますが、もう一度独自の StockTicker.html ファイルを実行する場合は、スクリプト タグの jQuery 参照の更新が必要となることがあります。

アプリケーションの実行

最初のアプリで見た表には、便利な機能がありました。 完全な株価ティッカー アプリケーションが示す新機能は、水平方向にスクロールするウィンドウに値上がりか値下がりかによって色を変えて株式データと株式を表示するというものです。

  1. F5 キーを押してアプリを実行します。

    アプリを初めて実行する場合、"market" は "closed" となっており、静的テーブルとスクロールしていないティッカー ウィンドウが表示されます。

  2. [市場を開く] を選択します。

    Screenshot of the live ticker.

    • ライブ株価ティッカー ボックスが水平方向にスクロールし始め、サーバーでは、株価のランダムな変動が定期的にブロードキャストされ始めます。

    • 株価が変動するたびに、アプリでは、ライブ株価一覧ライブ株価ティッカーの両方が更新されます。

    • 株価の変動がプラスの場合、アプリには、株価が緑色の背景で表示されます。

    • 株価の変動がマイナスの場合、アプリには、株価が赤色の背景で表示されます。

  3. [市場を閉じる] を選択します。

    • 表の更新が停止します。

    • ティッカーのスクロールが停止します。

  4. リセットを選択します。

    • すべての株式データがリセットされます。

    • アプリでは、株価の変動が始まる前の初期状態への復元が行われます。

  5. ブラウザーから URL をコピーして、ブラウザーを 2 つ新たに開き、URL をアドレス バーに貼り付けます。

  6. 各ブラウザーで、同じデータが同時に動的に更新されます。

  7. コントロールのいずれかを選択すると、すべてのブラウザーが同時に同じように応答します。

ライブ株価ティッカー ディスプレイ

ライブ株価ティッカー ディスプレイは、CSS スタイルで 1 行に書式設定された <div> 要素内にある、順序が付けられていない一覧です。 このアプリでは、<li> テンプレート文字列内のプレースホルダーを置き換え、<li> 要素を <ul> 要素に動的に追加する方法と同じ方法で、ティッカーが初期化および更新されます。 このアプリには、<div> 内にある順序が付けられていない一覧の左余白を変更する jQuery animate 関数を使用したスクロール機能が含まれています。

SignalR.Sample StockTicker.html

株価ティッカー HTML コード:

<h2>Live Stock Ticker</h2>
<div id="stockTicker">
    <div class="inner">
        <ul>
            <li class="loading">loading...</li>
        </ul>
    </div>
</div>

SignalR.Sample StockTicker.css

株価ティッカー CSS コード:

#stockTicker {
    overflow: hidden;
    width: 450px;
    height: 24px;
    border: 1px solid #999;
    }

    #stockTicker .inner {
        width: 9999px;
    }

    #stockTicker ul {
        display: inline-block;
        list-style-type: none;
        margin: 0;
        padding: 0;
    }

    #stockTicker li {
        display: inline-block;
        margin-right: 8px;   
    }

    /*<li data-symbol="{Symbol}"><span class="symbol">{Symbol}</span><span class="price">{Price}</span><span class="change">{PercentChange}</span></li>*/
    #stockTicker .symbol {
        font-weight: bold;
    }

    #stockTicker .change {
        font-style: italic;
    }

SignalR.Sample SignalR.StockTicker.js

スクロールさせる jQuery コード:

function scrollTicker() {
    var w = $stockTickerUl.width();
    $stockTickerUl.css({ marginLeft: w });
    $stockTickerUl.animate({ marginLeft: -w }, 15000, 'linear', scrollTicker);
}

クライアントが呼び出すことができるサーバー上の追加のメソッド

アプリの柔軟性を高めるために、アプリで呼び出すことができるメソッドが追加されています。

SignalR.Sample StockTickerHub.cs

StockTickerHub クラスを使用すると、クライアントで呼び出すことができる追加の 4 つのメソッドが定義されます。

public string GetMarketState()
{
    return _stockTicker.MarketState.ToString();
}

public void OpenMarket()
{
    _stockTicker.OpenMarket();
}

public void CloseMarket()
{
    _stockTicker.CloseMarket();
}

public void Reset()
{
    _stockTicker.Reset();
}

アプリは、ページの上部にあるボタンに応答して、OpenMarketCloseMarketReset を呼び出します。 これは、すべてのクライアントに直ちに反映された状態の変更をトリガーしている 1 つのクライアントのパターンを示しています。 これらの各メソッドでは、市場の状態の変化を引き起こす StockTicker クラスのメソッドを呼び出し、新しい状態をブロードキャストします。

SignalR.Sample StockTicker.cs

StockTicker クラスの場合、アプリでは、MarketState 列挙値を返す MarketState プロパティを使用して市場の状態が維持されます。

public MarketState MarketState
{
    get { return _marketState; }
    private set { _marketState = value; }
}

public enum MarketState
{
    Closed,
    Open
}

StockTicker クラスはスレッドセーフである必要があるため、市場の状態を変更するメソッドではそれぞれ、ロック ブロック内で行われます。

public void OpenMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Open)
        {
            _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
            MarketState = MarketState.Open;
            BroadcastMarketStateChange(MarketState.Open);
        }
    }
}

public void CloseMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState == MarketState.Open)
        {
            if (_timer != null)
            {
                _timer.Dispose();
            }
            MarketState = MarketState.Closed;
            BroadcastMarketStateChange(MarketState.Closed);
        }
    }
}

public void Reset()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Closed)
        {
            throw new InvalidOperationException("Market must be closed before it can be reset.");
        }
        LoadDefaultStocks();
        BroadcastMarketReset();
    }
}

このコードが確実にスレッド セーフであるように、MarketState プロパティをバックする _marketState フィールドによって、volatile と指定されました。

private volatile MarketState _marketState;

BroadcastMarketStateChange メソッドと BroadcastMarketReset メソッドは、既に確認した BroadcastStockPrice メソッドと似ています。ただし、クライアントで定義されているさまざまなメソッドが呼び出されるという点で異なります。

private void BroadcastMarketStateChange(MarketState marketState)
{
    switch (marketState)
    {
        case MarketState.Open:
            Clients.All.marketOpened();
            break;
        case MarketState.Closed:
            Clients.All.marketClosed();
            break;
        default:
            break;
    }
}

private void BroadcastMarketReset()
{
    Clients.All.marketReset();
}

サーバーが呼び出すことができるクライアント上の追加の関数

updateStockPrice 関数を使用すると、表とティッカー ディスプレイの両方を処理できるようになったことに加え、jQuery.Color を使用して赤色と緑色で点滅させることができるようになりました。

SignalR.StockTicker.js の新しい関数を使用すると、市場の状態に基づいてボタンを有効または無効にすることができます。 また、ライブ株価ティッカーの水平スクロールが停止または開始します。 多くの関数が ticker.client に追加されているため、このアプリでは、jQuery 拡張関数を使用して関数を追加しています。

$.extend(ticker.client, {
    updateStockPrice: function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock)),
            $li = $(liTemplate.supplant(displayStock)),
            bg = stock.LastChange === 0
                ? '255,216,0' // yellow
                : stock.LastChange > 0
                    ? '154,240,117' // green
                    : '255,148,148'; // red

        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        $stockTickerUl.find('li[data-symbol=' + stock.Symbol + ']')
            .replaceWith($li);

        $row.flash(bg, 1000);
        $li.flash(bg, 1000);
    },

    marketOpened: function () {
        $("#open").prop("disabled", true);
        $("#close").prop("disabled", false);
        $("#reset").prop("disabled", true);
        scrollTicker();
    },

    marketClosed: function () {
        $("#open").prop("disabled", false);
        $("#close").prop("disabled", true);
        $("#reset").prop("disabled", false);
        stopTicker();
    },

    marketReset: function () {
        return init();
    }
});

接続を確立した後のクライアントの追加の設定

クライアントでは、接続が確立された後に、いくつかの追加の作業があります。

  • 適切な marketOpened 関数または marketClosed 関数を呼び出すために、市場が開いているか閉じられているかを確認します。

  • サーバー メソッド呼び出しをボタンにアタッチします。

$.connection.hub.start()
    .pipe(init)
    .pipe(function () {
        return ticker.server.getMarketState();
    })
    .done(function (state) {
        if (state === 'Open') {
            ticker.client.marketOpened();
        } else {
            ticker.client.marketClosed();
        }

        // Wire up the buttons
        $("#open").click(function () {
            ticker.server.openMarket();
        });

        $("#close").click(function () {
            ticker.server.closeMarket();
        });

        $("#reset").click(function () {
            ticker.server.reset();
        });
    });

サーバー メソッドは、アプリで接続が確立されるまで、ボタンに接続されません。 そのため、サーバー メソッドは、コードを使用しても、使用可能になるまでは呼び出すことができません。

その他のリソース

このチュートリアルでは、サーバーからすべての接続済みクライアントへメッセージをブロードキャストする SignalR アプリケーションのプログラミング方法について説明しました。 これで、メッセージのブロードキャストを、定期的に、あらゆるクライアントからの通知に応答して行うことができるようになりました。 マルチスレッド シングルトン インスタンスの概念を使用すれば、マルチプレーヤー のオンライン ゲーム シナリオでサーバーの状態を維持できます。 例については、SignalR に基づく ShootR ゲームに関するページを参照してください。

ピア ツー ピア通信のシナリオを示すチュートリアルについては、SignalR の概要および SignalR を使用したリアルタイム更新に関する記事を参照してください。

SignalR の詳細については、次のリソースを参照してください。

次のステップ

このチュートリアルでは、次の作業を行いました。

  • プロジェクトを作成しました
  • サーバー コードを設定する
  • サーバー コードを調べました
  • クライアント コードを設定する
  • クライアント コードを調べました
  • アプリケーションをテストする
  • ログ記録を有効にしました

ASP.NET SignalR 2 を使用するリアルタイム Web アプリケーションの作成方法を学習するには、次の記事に進んでください。