チュートリアル: ASP.NET SignalR 1.x によるサーバー ブロードキャスト

作成者: Patrick FletcherTom Dykstra

警告

このドキュメントは、SignalR の最新バージョン用ではありません。 SignalR の ASP.NET Coreを見てみましょう。

このチュートリアルでは、ASP.NET SignalR を使用してサーバー ブロードキャスト機能を提供する Web アプリケーションを作成する方法について説明します。 サーバー ブロードキャストとは、クライアントに送信される通信がサーバーによって開始されることを意味します。 このシナリオでは、クライアントに送信される通信が 1 つ以上のクライアントによって開始される、チャット アプリケーションなどのピアツーピア シナリオとは異なるプログラミングアプローチが必要です。

このチュートリアルで作成するアプリケーションは、サーバー ブロードキャスト機能の一般的なシナリオである株式ティッカーをシミュレートします。

チュートリアルに関するコメントは歓迎されています。 チュートリアルに直接関連していない質問がある場合は、 ASP.NET SignalR フォーラム または StackOverflow.com に投稿できます。

概要

Microsoft.AspNet.SignalR.Sample NuGet パッケージは、シミュレートされたストック ティッカー アプリケーションのサンプルを Visual Studio プロジェクトにインストールします。 このチュートリアルの最初の部分では、そのアプリケーションの簡略化されたバージョンを最初から作成します。 チュートリアルの残りの部分では、NuGet パッケージをインストールし、作成する追加の機能とコードを確認します。

ストック ティッカー アプリケーションは、サーバーから接続されているすべてのクライアントに定期的に通知を "プッシュ" またはブロードキャストする、一種のリアルタイム アプリケーションの代表です。

このチュートリアルの最初の部分でビルドするアプリケーションには、ストック データを含むグリッドが表示されます。

StockTicker の初期バージョン

サーバーは定期的に株価をランダムに更新し、接続されているすべてのクライアントに更新をプッシュします。 ブラウザーでは、サーバーからの通知に応じて、[ 変更 ] 列と列の数値と % 記号が動的に変更されます。 同じ URL に追加のブラウザーを開くと、すべて同じデータと同じ変更がデータに同時に表示されます。

このチュートリアルには、次のセクションが含まれています。

注意

アプリケーションをビルドする手順を実行しない場合は、SignalR.Sample パッケージを新しい Empty ASP.NET Web Application プロジェクトにインストールし、次の手順を読んでコードの説明を取得できます。 このチュートリアルの最初の部分では SignalR.Sample コードのサブセットについて説明し、2 番目のパートでは SignalR.Sample パッケージの追加機能の主な機能について説明します。

必須コンポーネント

開始する前に、コンピューターに Visual Studio 2012 または 2010 SP1 がインストールされていることを確認します。 Visual Studio をお持ちでない場合は、「 ASP.NET ダウンロード 」を参照して、無料の Visual Studio 2012 Express for Web を入手してください。

Visual Studio 2010 をお持ちの場合は、 NuGet がインストールされていることを確認してください。

プロジェクトを作成する

  1. [ ファイル ] メニューの [ 新しいプロジェクト] をクリックします。

  2. [新しいプロジェクト] ダイアログ ボックスで、[テンプレート] の下の [C#] を展開し、[Web] を選択します

  3. [ 空の Web アプリケーション] テンプレート ASP.NET 選択し、プロジェクト に SignalR.StockTicker という名前を付け、[OK] をクリック します

    [新しいプロジェクト] ダイアログ ボックス

SignalR NuGet パッケージを追加する

SignalR と JQuery NuGet パッケージを追加する

プロジェクトに SignalR 機能を追加するには、NuGet パッケージをインストールします。

  1. [ ツール] |NuGet パッケージ マネージャー |パッケージ マネージャー コンソール

  2. パッケージ マネージャーで次のコマンドを入力します。

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    SignalR パッケージは、他の多くの NuGet パッケージを依存関係としてインストールします。 インストールが完了すると、ASP.NET アプリケーションで SignalR を使用するために必要なすべてのサーバーコンポーネントとクライアント コンポーネントがあります。

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

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

Stock クラスを作成する

まず、在庫に関する情報を格納および送信するために使用する Stock モデル クラスを作成します。

  1. プロジェクト フォルダーに新しいクラス ファイルを作成し、 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);
                }
            }
        }
    }
    

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

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

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

StockTicker からのブロードキャスト

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

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

  2. ASP.NET and Web Tools 2012.2 Update を使用する Visual Studio 2012 がある場合は、[Visual C#] の [Web] をクリックし、[SignalR Hub クラス] 項目テンプレートを選択します。 それ以外の場合は、 クラス テンプレートを選択します。

  3. 新しいクラス に StockTickerHub.cs という名前を付け、[ 追加] をクリックします。

    StockTickerHub.cs を追加する

  4. テンプレート コードを次のコードに置き換えます。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    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();
            }
        }
    }
    

    Hub クラスは、クライアントがサーバー上で呼び出すことができるメソッドを定義するために使用されます。 次の 1 つのメソッドを定義しています。 GetAllStocks() クライアントは、最初にサーバーに接続するときに、このメソッドを呼び出して、現在の価格を持つすべての株式の一覧を取得します。 メソッドは、メモリからデータを返しているため、同期的に実行して を返 IEnumerable<Stock> すことができます。 データベース参照や Web サービス呼び出しなどの待機を伴う処理を行ってデータを取得する必要がある場合は、非同期処理を有効にする戻り値として を指定 Task<IEnumerable<Stock>> します。 詳細については、「 ASP.NET SignalR Hubs API ガイド - サーバー - 非同期実行のタイミング」を参照してください。

    HubName 属性は、クライアント上の JavaScript コードでハブを参照する方法を指定します。 この属性を使用しない場合のクライアントの既定の名前は、クラス名のキャメル ケース バージョンです。この場合は stockTickerHub になります。

    後で StockTicker クラスを作成するとわかるように、そのクラスのシングルトン インスタンスが静的な Instance プロパティに作成されます。 StockTicker のシングルトン インスタンスは、接続または切断されたクライアントの数に関係なくメモリ内に残り、そのインスタンスは GetAllStocks メソッドが現在の株式情報を返すために使用するものです。

  5. プロジェクト フォルダーに新しいクラス ファイルを作成し、 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 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 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 フィールドを初期化します。コンストラクターがプライベートとしてマークされているため、作成できるクラスのインスタンスはこれが唯一です。 遅延初期化 は、パフォーマンス上の理由ではなく、インスタンスの作成がスレッド セーフであることを確認するために、_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 は株価を返します。 前に説明したように、この株式のコレクションは StockTickerHub.GetAllStocks によって返されます。これは、クライアントが呼び出すことができる Hub クラスのサーバー メソッドです。

    private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
    private StockTicker(IHubConnectionContext 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;
    }
    

    UpdateStockPrices はタイマーによって呼び出され、状態パラメーターに null が渡されます。 価格を更新する前に、_updateStockPricesLock オブジェクトに対してロックが設定されます。 このコードは、別のスレッドが既に価格を更新しているかどうかを確認し、リスト内の各在庫に対して TryUpdateStockPrice を呼び出します。 TryUpdateStockPrice メソッドは、株価を変更するかどうか、および変更する金額を決定します。 株価が変更された場合、BroadcastStockPrice が呼び出され、接続されているすべてのクライアントに株価の変更がブロードキャストされます。

    _updatingStockPrices フラグは 揮発性 としてマークされ、アクセスがスレッド セーフであることを確認します。

    private volatile bool _updatingStockPrices = false;
    

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

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

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

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

    コンテキストを 1 回だけ取得する理由は 2 つあります。コンテキストの取得はコストの高い操作であり、一度取得すると、クライアントに送信されるメッセージの意図した順序が確実に保持されます。

    private readonly static Lazy<StockTicker> _instance =
        new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
    private StockTicker(IHubConnectionContext clients)
    {
        Clients = clients;
    
        // Remainder of constructor ...
    }
    
    private IHubConnectionContext 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 に転送するかを把握する必要があります。 これを行うには、 Global.asax ファイルにいくつかのコードを追加します。

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

  2. [ グローバル アプリケーション クラス ] 項目テンプレートを選択し、[ 追加] をクリックします。

    global.asax を追加する

  3. SignalR ルート登録コードを Application_Start メソッドに追加します。

    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
    }
    

    既定では、すべての SignalR トラフィックのベース URL は "/signalr" であり、"/signalr/hubs" を使用して、アプリケーション内にあるすべてのハブのプロキシを定義する動的に生成された JavaScript ファイルを取得します。 MapHubs メソッドには、 HubConfiguration クラスのインスタンスで別のベース URL と特定の SignalR オプションを指定できるオーバーロードが含まれています。

  4. ファイルの先頭に using ステートメントを追加します。

    using System.Web.Routing;
    
  5. Global.asax ファイルを保存して閉じ、プロジェクトをビルドします。

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

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

  1. プロジェクト フォルダーに新しい HTML ファイルを作成し 、StockTicker.htmlという 名前を付けます。

  2. テンプレート コードを次のコードに置き換えます。

    <!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.8.2.min.js" ></script>
        <!--Reference the SignalR library. -->
        <script src="/Scripts/jquery.signalR-1.0.1.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 つの列、ヘッダー行、および 5 つの列すべてにまたがる単一のセルを含むデータ行を含むテーブルを作成します。 データ行に "読み込み中.." と表示されます。と は、アプリケーションの起動時にのみ一時的に表示されます。 JavaScript コードでは、その行が削除され、サーバーから取得されたストック データを含む場所の行が追加されます。

    スクリプト タグは、jQuery スクリプト ファイル、SignalR コア スクリプト ファイル、SignalR プロキシ スクリプト ファイル、および後で作成する StockTicker スクリプト ファイルを指定します。 SignalR プロキシ スクリプト ファイルは、"/signalr/hubs" URL を指定し、動的に生成され、Hub クラスのメソッドのプロキシ メソッド (この場合は StockTickerHub.GetAllStocks) を定義します。 必要に応じて、 SignalR ユーティリティ を使用してこの JavaScript ファイルを手動で生成し、MapHubs メソッド呼び出しで動的なファイル作成を無効にすることができます。

  3. 重要

    StockTicker.htmlの JavaScript ファイル参照が正しいことを確認します。 つまり、スクリプト タグの jQuery バージョン (例では 1.8.2) がプロジェクトの Scripts フォルダー内の jQuery バージョンと同じであることを確認し、スクリプト タグの SignalR バージョンがプロジェクトの Scripts フォルダー内の SignalR バージョンと同じであることを確認します。 必要に応じて、スクリプト タグ内のファイル名を変更します。

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

  5. プロジェクト フォルダーに新しい JavaScript ファイルを作成し 、StockTicker.jsという 名前を付けます。

  6. テンプレート コードを次のコードに置き換えます。

    // 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 クラスのプロキシへの参照を取得し、それをティッカー変数に格納します。 プロキシ名は、[HubName] 属性によって設定された名前です。

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

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

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

    init 関数は、サーバー上の getAllStocks 関数を呼び出し、サーバーが返す情報を使用してストック テーブルを更新します。 既定では、クライアントではキャメル ケースを使用する必要がありますが、メソッド名はサーバーでパスカル大文字と小文字が付けられます。 キャメル ケースルールは、オブジェクトではなくメソッドにのみ適用されます。 たとえば、在庫を参照するとします。シンボルとストック。価格。stock.symbol または stock.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();
    }
    

    クライアントでパスカルの大文字と小文字を使用する場合、またはまったく異なるメソッド名を使用する場合は、Hub クラス自体を HubName 属性で装飾するのと同じ方法で、HubMethodName 属性を使用して Hub メソッドを装飾できます。

    init メソッドでは、formatStock を呼び出してストック オブジェクトのプロパティを書式設定し、rowTemplate 変数のプレースホルダーをストック オブジェクト のプロパティ値に置き換えるために supplant ( StockTicker.jsの上部で定義されている) を呼び出すことによって、サーバーから受け取ったストック オブジェクトごとにテーブル行の HTML が作成されます。 その後、結果の HTML がストック テーブルに追加されます。

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

    サーバーは、株式の価格を変更すると、接続されているクライアントで updateStockPrice を呼び出します。 この関数は、サーバーからの呼び出しで使用できるようにするために、stockTicker プロキシの client プロパティに追加されます。

    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. F5 キーを押してデバッグ モードでアプリケーションを実行します。

    ストック テーブルには、最初に "loading... " が表示されます。その後、短い遅延後に初期在庫データが表示され、株価が変化し始めます。

    読み込み

    初期在庫テーブル

    サーバーから変更を受け取るストック テーブル

  2. ブラウザーのアドレス バーから URL をコピーし、1 つ以上の新しいブラウザー ウィンドウに貼り付けます。

    初期在庫表示は最初のブラウザと同じで、変更は同時に行われます。

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

    StockTicker シングルトン オブジェクトは引き続きサーバーで実行されているため、株価テーブルの表示には、株式が引き続き変更されたことを示しています。 (変更数値が 0 の最初のテーブルは表示されません)。

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

ログの有効化

SignalR には、トラブルシューティングに役立つクライアントで有効にできるログ機能が組み込まれています。 このセクションでは、ログ記録を有効にし、SignalR で使用されている次のトランスポート方法をログで確認する方法を示す例を確認します。

任意の接続に対して、SignalR はサーバーとクライアントの両方でサポートされる最適なトランスポート方法を選択します。

  1. StockTicker.js開き、ファイルの末尾で接続を初期化するコードの直前にログ記録を有効にするコード行を追加します。

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

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

    Windows 8 (IIS 8) でインターネット エクスプローラー 10 を実行している場合、トランスポート方法は WebSocket です。

    IE 10 IIS 8 コンソール

    Windows 7 (IIS 7.5) でインターネット エクスプローラー 10 を実行している場合、トランスポート方法は iframe です。

    IE 10 コンソール、IIS 7.5

    Firefox で Firebug アドインをインストールして、コンソール ウィンドウを取得します。 Windows 8 (IIS 8) で Firefox 19 を実行している場合、トランスポートメソッドは WebSocket です。

    Firefox 19 IIS 8 Websockets

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

    Firefox 19 IIS 7.5 コンソール

完全な StockTicker サンプルをインストールして確認する

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

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

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

  2. [NuGet パッケージの管理] ダイアログ ボックスで、[オンライン] をクリックし、[オンライン検索] ボックスに「SignalR.Sample」と入力し、SignalR.Sample パッケージの [インストール] をクリックします。

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

  3. Global.asax ファイルで、RouteTable.Routes.MapHubs()をコメント アウトします。Application_Start メソッドで前に追加した行。

    SignalR.Sample パッケージは SignalR ルートを App_Start/RegisterHubs.cs ファイルに登録するため、Global.asax のコードは不要です。

    [assembly: WebActivator.PreApplicationStartMethod(typeof(SignalR.StockTicker.RegisterHubs), "Start")]
    
    namespace SignalR.StockTicker
    {
        public static class RegisterHubs
        {
            public static void Start()
            {
                // Register the default hubs route: ~/signalr/hubs
                RouteTable.Routes.MapHubs();
            }
        }
    }
    

    assembly 属性によって参照される WebActivator クラスは、SignalR.Sample パッケージの依存関係としてインストールされる WebActivatorEx NuGet パッケージに含まれています。

  4. ソリューション エクスプローラーで、SignalR.Sample パッケージをインストールして作成した SignalR.Sample フォルダーを展開します。

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

    注意

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

アプリケーションの実行

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

    前に確認したグリッドに加えて、完全在庫ティッカー アプリケーションには、同じストック データを表示する水平スクロール ウィンドウが表示されます。 アプリケーションを初めて実行すると、"market" が "閉じられました" と、静的グリッドとスクロールしていないティッカー ウィンドウが表示されます。

    StockTicker 画面の開始

    [市場を開く] をクリックすると、[Live Stock Ticker] ボックスが水平方向にスクロールし始め、サーバーは株価の変化をランダムに定期的にブロードキャストし始めます。 株価が変わるたびに、[ ライブ株価表] グリッドと [ ライブ在庫ティッカー ] ボックスの両方が更新されます。 株価の変化が正の場合、株価は緑色の背景で表示され、変更が負の場合、株価は赤い背景で表示されます。

    StockTicker アプリ、市場オープン

    [ 市場を閉じる ] ボタンは変更を停止し、ティッカーのスクロールを停止し、[ リセット ] ボタンをクリックすると、価格の変更が開始される前にすべての在庫データが初期状態にリセットされます。 ブラウザー ウィンドウをさらに開き、同じ URL に移動すると、同じデータが各ブラウザーで同時に動的に更新されます。 いずれかのボタンをクリックすると、すべてのブラウザーが同時に同じ方法で応答します。

ライブストックティッカーディスプレイ

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

株価情報 HTML:

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

株価情報 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;
    }

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

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

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

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

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

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

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

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

ページの上部にあるボタンに応じて、OpenMarket、CloseMarket、および Reset が呼び出されます。 これらは、すべてのクライアントに直ちに反映される状態の変更をトリガーする 1 つのクライアントのパターンを示しています。 これらの各メソッドは、市場状態の変化に影響を及ぼす StockTicker クラスのメソッドを呼び出し、新しい状態をブロードキャストします。

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 アプリケーションをプログラミングする方法について説明しました。 マルチスレッド シングルトン インスタンスを使用してサーバーの状態を維持するパターンは、マルチプレーヤー オンライン ゲームのシナリオでも使用できます。 例については、 SignalR に基づく ShootR ゲームを参照してください。

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

SignalR 開発のより高度な概念については、SignalR のソース コードとリソースに関する次のサイトを参照してください。