Sdílet prostřednictvím


Kurz: Serverové vysílání s knihovnou ASP.NET SignalR 1.x

Patrick Fletcher, Tom Dykstra

Upozornění

Tato dokumentace není určená pro nejnovější verzi služby SignalR. Podívejte se na ASP.NET Core SignalR.

V tomto kurzu se dozvíte, jak vytvořit webovou aplikaci, která používá ASP.NET SignalR k poskytování funkcí serverového vysílání. Serverové všesměrové vysílání znamená, že komunikace odesílaná klientům je inicializována serverem. Tento scénář vyžaduje jiný programovací přístup než scénáře typu peer-to-peer, jako jsou chatovací aplikace, ve kterých je komunikace odesílaná klientům inicializována jedním nebo více klienty.

Aplikace, kterou v tomto kurzu vytvoříte, simuluje burzovní ticker, což je typický scénář pro funkci serverového vysílání.

Komentáře ke kurzu jsou vítány. Pokud máte dotazy, které nesouvisejí přímo s kurzem, můžete je publikovat na fóru ASP.NET SignalR nebo StackOverflow.com.

Přehled

Balíček NuGet Microsoft.AspNet.SignalR.Sample nainstaluje ukázkovou simulovanou aplikaci akcií v projektu sady Visual Studio. V první části tohoto kurzu vytvoříte zjednodušenou verzi této aplikace od začátku. Ve zbývající části kurzu nainstalujete balíček NuGet a zkontrolujete další funkce a kód, které vytvoří.

Aplikace burzovního tickeru představuje druh aplikace v reálném čase, ve které chcete pravidelně "odesílat" nebo vysílat oznámení ze serveru všem připojeným klientům.

Aplikace, kterou vytvoříte v první části tohoto kurzu, zobrazuje mřížku s daty o akciích.

Počáteční verze StockTicker

Server pravidelně náhodně aktualizuje ceny akcií a nasdílí je všem připojeným klientům. V prohlížeči se čísla a symboly ve sloupcích Change a % dynamicky mění v reakci na oznámení ze serveru. Pokud otevřete další prohlížeče se stejnou adresou URL, všechny zobrazují stejná data a stejné změny dat současně.

Tento kurz obsahuje následující části:

Poznámka

Pokud nechcete procházet kroky sestavení aplikace, můžete nainstalovat balíček SignalR.Sample do nového projektu Empty ASP.NET Web Application a přečíst si tyto kroky, abyste získali vysvětlení kódu. První část kurzu se zabývá podmnožinou kódu SignalR.Sample a druhá část vysvětluje klíčové funkce dalších funkcí v balíčku SignalR.Sample.

Požadavky

Než začnete, ujistěte se, že máte v počítači nainstalovanou sadu Visual Studio 2012 nebo 2010 SP1. Pokud sadu Visual Studio nemáte, podívejte se na ASP.NET soubory ke stažení a získejte bezplatnou sadu Visual Studio 2012 Express for Web.

Pokud máte Visual Studio 2010, ujistěte se, že je nainstalovaný NuGet .

Vytvoření projektu

  1. V nabídce Soubor klikněte na Nový projekt.

  2. V dialogovém okně Nový projekt rozbalte C# v části Šablony a vyberte Web.

  3. Vyberte šablonu ASP.NET Prázdná webová aplikace , pojmenujte projekt SignalR.StockTicker a klikněte na OK.

    Dialogové okno Nový projekt

Přidání balíčků SignalR NuGet

Přidání balíčků SignalR a JQuery NuGet

Funkci SignalR můžete do projektu přidat instalací balíčku NuGet.

  1. Klikněte na Nástroje | Správce balíčků NuGet | Konzola Správce balíčků.

  2. Ve správci balíčků zadejte následující příkaz.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Balíček SignalR nainstaluje řadu dalších balíčků NuGet jako závislostí. Po dokončení instalace budete mít všechny součásti serveru a klienta potřebné k používání služby SignalR v ASP.NET aplikaci.

Nastavení kódu serveru

V této části nastavíte kód, který běží na serveru.

Vytvoření třídy Stock

Začnete vytvořením třídy modelu Stock, kterou použijete k ukládání a přenosu informací o akciích.

  1. Ve složce projektu vytvořte nový soubor třídy, pojmenujte ho Stock.cs a pak nahraďte kód šablony následujícím kódem:

    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);
                }
            }
        }
    }
    

    Dvě vlastnosti, které nastavíte při vytváření akcií, jsou Symbol (například MSFT pro Microsoft) a Cena. Další vlastnosti závisí na tom, jak a kdy nastavíte Price. Při prvním nastavení ceny se hodnota rozšíří na DayOpen. Další časy, kdy nastavíte Price, hodnoty vlastnosti Change a PercentChange se počítají na základě rozdílu mezi Price a DayOpen.

Vytvoření tříd StockTicker a StockTickerHub

K interakci mezi servery a klienty použijete rozhraní API služby SignalR Hub. Třída StockTickerHub odvozená z třídy SignalR Hub bude zpracovávat příjem připojení a volání metod od klientů. Potřebujete také udržovat data zásob a spouštět objekt Časovač, který bude pravidelně spouštět aktualizace cen, a to nezávisle na klientských připojeních. Tyto funkce nemůžete umístit do třídy Hub, protože instance centra jsou přechodné. Instance třídy Hub se vytvoří pro každou operaci v centru, jako jsou připojení a volání z klienta na server. Takže mechanismus, který uchovává data o akciích, aktualizuje ceny a vysílá aktualizace cen, musí běžet v samostatné třídě, kterou pojmenujete StockTicker.

Vysílání z Burzovního tickeru

Chcete, aby na serveru běžela pouze jedna instance třídy StockTicker, takže budete muset nastavit odkaz z každé instance StockTickerHub na jednu instanci StockTicker. Třída StockTicker musí být schopna vysílat do klientů, protože obsahuje data o akciích a aktivuje aktualizace, ale StockTicker není třída Centra. Proto musí třída StockTicker získat odkaz na objekt kontextu připojení centra SignalR. Pak může použít objekt kontextu připojení SignalR k vysílání do klientů.

  1. V Průzkumník řešení klikněte pravým tlačítkem na projekt a klikněte na Přidat novou položku.

  2. Pokud máte Visual Studio 2012 s aktualizací ASP.NET and Web Tools 2012.2 Update, klikněte v části Visual C# na Web a vyberte šablonu položky Třída centra SignalR. V opačném případě vyberte šablonu Třída .

  3. Pojmenujte novou třídu StockTickerHub.cs a klikněte na Přidat.

    Přidat StockTickerHub.cs

  4. Kód šablony nahraďte následujícím kódem:

    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();
            }
        }
    }
    

    Třída Hub slouží k definování metod, které můžou klienti volat na serveru. Definujete jednu metodu: GetAllStocks(). Když se klient poprvé připojí k serveru, zavolá tuto metodu, aby získal seznam všech akcií s jejich aktuálními cenami. Metoda se může spustit synchronně a vrátit IEnumerable<Stock> , protože vrací data z paměti. Pokud by metoda musela získat data provedením něčeho, co by zahrnovalo čekání, například vyhledávání v databázi nebo volání webové služby, zadali Task<IEnumerable<Stock>> byste jako návratovou hodnotu, abyste umožnili asynchronní zpracování. Další informace najdete v tématu Příručka ASP.NET rozhraní API služby SignalR Hubs – Server – Kdy provést asynchronně.

    Atribut HubName určuje, jak se na centrum bude odkazovat v kódu JavaScriptu na klientovi. Výchozí název v klientovi, pokud tento atribut nepoužijete, je verze názvu třídy s velbloudími písmeny, což by v tomto případě bylo stockTickerHub.

    Jak uvidíte později při vytváření třídy StockTicker, v její statické vlastnosti Instance se vytvoří samostatná instance této třídy. Tato jediná instance StockTicker zůstává v paměti bez ohledu na to, kolik klientů se připojí nebo odpojí, a tato instance je to, co GetAllStocks metoda používá k vrácení aktuálních informací o akciích.

  5. Ve složce projektu vytvořte nový soubor třídy, pojmenujte ho StockTicker.cs a pak nahraďte kód šablony následujícím kódem:

    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);
            }
    
        }
    }
    

    Vzhledem k tomu, že více vláken bude spuštěna stejná instance kódu StockTicker, StockTicker třída musí být threadsafe.

    Uložení instance typu singleton do statického pole

    Kód inicializuje statické _instance pole, které podporuje vlastnost Instance s instancí třídy, a to je jediná instance třídy, která může být vytvořena, protože konstruktor je označen jako soukromý. Opožděná inicializace se používá pro pole _instance, nikoli z důvodů výkonu, ale k zajištění, že vytvoření instance je bezpečné pro přístup z více vláken.

    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
    public static StockTicker Instance
    {
        get
        {
            return _instance.Value;
        }
    }
    

    Pokaždé, když se klient připojí k serveru, nová instance Třídy StockTickerHub spuštěné v samostatném vlákně získá instanci StockTicker singleton ze statické vlastnosti StockTicker.Instance, jak jste viděli dříve ve třídě StockTickerHub.

    Ukládání burzovních dat v ConcurrentDictionary

    Konstruktor inicializuje kolekci _stocks s některými ukázkovými daty akcií a GetAllStocks vrátí akcie. Jak jste viděli dříve, tuto kolekci akcií zase vrátí StockTickerHub.GetAllStocks, což je serverová metoda ve třídě Hub, kterou můžou volat klienti.

    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;
    }
    

    Kolekce akcie je definována jako typ ConcurrentDictionary pro bezpečnost vláken. Jako alternativu můžete použít objekt Dictionary a při provádění změn slovník explicitně uzamknout.

    Pro tuto ukázkovou aplikaci je v pořádku ukládat data aplikace do paměti a při odstranění instance StockTicker tato data ztratit. Ve skutečné aplikaci byste pracovali s back-endovým úložištěm dat, jako je databáze.

    Pravidelné aktualizace cen akcií

    Konstruktor spustí objekt Timer, který pravidelně volá metody, které náhodně aktualizují ceny akcií.

    _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 je volána časovačem, který předává hodnotu null v parametru stavu. Před aktualizací cen se u objektu _updateStockPricesLock zamkne zámek. Kód zkontroluje, jestli jiné vlákno už aktualizuje ceny, a pak zavolá TryUpdateStockPrice pro každou akcii v seznamu. Metoda TryUpdateStockPrice rozhoduje, jestli chcete změnit cenu akcií a kolik ji změnit. Pokud se změní cena akcií, zavolá se BroadcastStockPrice, aby se změna ceny akcií vysílala všem připojeným klientům.

    Příznak _updatingStockPrices je označen jako nestálý , aby se zajistilo, že přístup k němu je bezpečný.

    private volatile bool _updatingStockPrices = false;
    

    Ve skutečné aplikaci TryUpdateStockPrice metoda by volala webovou službu k vyhledání ceny; v tomto kódu používá generátor náhodných čísel k náhodnému provádění změn.

    Získání kontextu SignalR, aby třída StockTicker mohl vysílat klientům

    Vzhledem k tomu, že změny ceny pocházejí z objektu StockTicker, je to objekt, který musí volat metodu updateStockPrice na všech připojených klientech. Ve třídě Hub máte rozhraní API pro volání klientských metod, ale StockTicker neodvozuje z třídy Hub a nemá odkaz na žádný objekt Centra. Proto, aby všesměrové vysílání do připojených klientů, StockTicker třída musí získat instanci kontextu SignalR pro Třídu StockTickerHub a použít ji k volání metod na klientech.

    Kód získá odkaz na kontext SignalR při vytvoření instance jednoúčelové třídy, předá tento odkaz konstruktoru a konstruktor ho vloží do vlastnosti Clients.

    Existují dva důvody, proč chcete získat kontext jenom jednou: získání kontextu je nákladná operace a jeho získání jednou zajistí zachování zamýšleného pořadí zpráv odesílaných klientům.

    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);
    }
    

    Získání vlastnosti Clients kontextu a jejího vložení do vlastnosti StockTickerClient umožňuje psát kód pro volání klientských metod, které vypadají stejně jako ve třídě Hub. Pokud chcete například vysílat všem klientům, můžete napsat Clients.All.updateStockPrice(stock).

    Metoda updateStockPrice, kterou voláte v BroadcastStockPrice ještě neexistuje; Přidáte ho později při psaní kódu, který běží na klientovi. Tady můžete odkazovat na updateStockPrice, protože Clients.All je dynamické, což znamená, že výraz se vyhodnotí za běhu. Když se toto volání metody spustí, SignalR odešle klientovi název metody a hodnotu parametru, a pokud má klient metodu s názvem updateStockPrice, zavolá se tato metoda a hodnota parametru se jí předá.

    Clients.All znamená odeslání všem klientům. SignalR nabízí další možnosti, jak určit, kterým klientům nebo skupinám klientů se má odesílat. Další informace najdete v tématu HubConnectionContext.

Registrace trasy SignalR

Server potřebuje vědět, kterou adresu URL má zachytit a směrovat na SignalR. K tomu přidáte kód do souboru Global.asax .

  1. V Průzkumník řešení klikněte pravým tlačítkem na projekt a potom klikněte na Přidat novou položku.

  2. Vyberte šablonu položky Globální třída aplikace a potom klikněte na Přidat.

    Přidat global.asax

  3. Do metody Application_Start přidejte registrační kód trasy SignalR:

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

    Ve výchozím nastavení je základní adresa URL pro veškerý provoz SignalR "/signalr" a "/signalr/hubs" se používá k načtení dynamicky generovaného souboru JavaScriptu, který definuje proxy servery pro všechna centra, která máte v aplikaci. Metoda MapHubs zahrnuje přetížení, která umožňují zadat jinou základní adresu URL a určité možnosti SignalR v instanci třídy HubConfiguration .

  4. Na začátek souboru přidejte příkaz using:

    using System.Web.Routing;
    
  5. Uložte a zavřete soubor Global.asax a sestavte projekt.

Dokončili jste nastavení kódu serveru. V další části nastavíte klienta.

Nastavení klientského kódu

  1. Ve složce projektu vytvořte nový soubor HTML a pojmenujte ho StockTicker.html.

  2. Kód šablony nahraďte následujícím kódem:

    <!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>
    

    Kód HTML vytvoří tabulku s 5 sloupci, řádkem záhlaví a datovým řádkem s jednou buňkou, která zahrnuje všech 5 sloupců. Řádek dat zobrazí "načítání..." a zobrazí se pouze v okamžiku, kdy se aplikace spustí. Kód JavaScriptu odebere tento řádek a na jeho místo přidá řádky s burzovními daty načtenými ze serveru.

    Značky skriptu určují soubor skriptu jQuery, soubor základního skriptu SignalR, soubor skriptu proxy serverů SignalR a soubor skriptu StockTicker, který vytvoříte později. Soubor skriptu proxy serverů SignalR, který určuje adresu URL /signalr/hubs, se dynamicky generuje a definuje metody proxy pro metody ve třídě Hub, v tomto případě pro StockTickerHub.GetAllStocks. Pokud chcete, můžete tento soubor JavaScriptu vygenerovat ručně pomocí nástrojů SignalR a zakázat dynamické vytváření souborů ve volání metody MapHubs.

  3. Důležité

    Ujistěte se, že javascriptový soubor odkazuje vStockTicker.html správně. To znamená, že se ujistěte, že verze jQuery ve vaší značce script (1.8.2 v tomto příkladu) je stejná jako verze jQuery ve složce Scripts vašeho projektu, a ujistěte se, že verze SignalR ve vaší značce script je stejná jako verze SignalR ve složce Scripts vašeho projektu. V případě potřeby změňte názvy souborů ve značkách skriptu.

  4. V Průzkumník řešení klikněte pravým tlačítkem na StockTicker.htmla potom klikněte na Nastavit jako úvodní stránku.

  5. Ve složce projektu vytvořte nový soubor JavaScriptu a pojmenujte hoStockTicker.js..

  6. Kód šablony nahraďte následujícím kódem:

    // 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 odkazuje na proxy služby SignalR. Kód získá odkaz na proxy pro třídu StockTickerHub a vloží ho do proměnné ticker. Název proxy serveru je název nastavený atributem [HubName]:

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

    Po definování všech proměnných a funkcí inicializuje poslední řádek kódu v souboru připojení SignalR voláním funkce SignalR start. Funkce start se spustí asynchronně a vrátí objekt jQuery Deferred, což znamená, že můžete volat hotovou funkci a určit funkci, která se má volat při dokončení asynchronní operace.

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

    Funkce init volá funkci getAllStocks na serveru a používá informace, které server vrátí, k aktualizaci burzovní tabulky. Všimněte si, že ve výchozím nastavení musíte na klientovi použít camel case, i když je název metody na serveru s písmeny pascal. Pravidlo pro vytváření malých a malých velikostí se vztahuje pouze na metody, ne na objekty. Například odkazujete na akcie. Symbol a akcie. Price, nikoli stock.symbol nebo 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();
    }
    

    Pokud byste chtěli na klientovi použít velikost písmen pascal nebo pokud chcete použít úplně jiný název metody, můžete metodu Hub opatřit atributem HubMethodName stejným způsobem, jakým jste ozdobili samotnou třídu Hub Pomocí atributu HubName.

    V metodě init se html pro řádek tabulky vytvoří pro každý burzovní objekt přijatý ze serveru voláním formatStock k formátování vlastností stock objektu a potom voláním metody supplant (která je definována v horní části StockTicker.js) nahradit zástupné symboly v proměnné rowTemplate hodnotami vlastností stock object. Výsledný kód HTML se pak připojí k burzovní tabulce.

    Funkci init zavoláte tak, že ji předáte jako funkci zpětného volání, která se spustí po dokončení asynchronní spouštěcí funkce. Pokud byste po volání start volali init jako samostatný příkaz Jazyka JavaScript, funkce by selhala, protože by se spustila okamžitě bez čekání na dokončení navazování připojení funkcí start. V takovém případě by se funkce init pokusila volat funkci getAllStocks před navázáním připojení k serveru.

    Když server změní cenu akcie, zavolá updateStockPrice na připojených klientech. Funkce se přidá do vlastnosti klienta proxy serveru stockTicker, aby byla dostupná pro volání ze serveru.

    ticker.client.updateStockPrice = function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock));
    
        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        }
    

    Funkce updateStockPrice formátuje burzovní objekt přijatý ze serveru do řádku tabulky stejným způsobem jako ve funkci init. Místo připojení řádku k tabulce ale najde aktuální řádek akcie v tabulce a nahradí ho novým řádkem.

Testování aplikace

  1. Stisknutím klávesy F5 spusťte aplikaci v režimu ladění.

    Burzovní tabulka zpočátku zobrazí "načítání..." po krátké prodlevě se zobrazí počáteční údaje o akciích a ceny akcií se začnou měnit.

    Načítání

    Počáteční burzovní tabulka

    Burzovní tabulka, která přijímá změny ze serveru

  2. Zkopírujte adresu URL z adresního řádku prohlížeče a vložte ji do jednoho nebo více nových oken prohlížeče.

    Počáteční burzovní zobrazení je stejné jako v prvním prohlížeči a změny probíhají současně.

  3. Zavřete všechny prohlížeče, otevřete nový prohlížeč a přejděte na stejnou adresu URL.

    Objekt Singleton StockTicker se na serveru stále spouští, takže zobrazení burzovní tabulky ukazuje, že se akcie stále mění. (Počáteční tabulka s nulovými údaji změn se nezobrazí.)

  4. Zavřete prohlížeč.

Povolit protokolování

SignalR má integrovanou funkci protokolování, kterou můžete povolit na klientovi a pomoct při řešení potíží. V této části povolíte protokolování a zobrazíte příklady, které ukazují, jak protokoly říkají, kterou z následujících metod přenosu používá SignalR:

Pro každé dané připojení zvolí SignalR nejlepší způsob přenosu, který podporuje server i klient.

  1. Otevřete StockTicker.js a přidejte řádek kódu, který povolí protokolování bezprostředně před kódem, který inicializuje připojení na konci souboru:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  2. Stisknutím klávesy F5 projekt spusťte.

  3. Otevřete okno vývojářských nástrojů prohlížeče a výběrem konzoly zobrazte protokoly. Možná budete muset stránku aktualizovat, aby se zobrazily protokoly služby Signalr, která vyjednává způsob přenosu pro nové připojení.

    Pokud používáte Internet Explorer 10 na Windows 8 (IIS 8), metoda přenosu je WebSockets.

    Konzola IE 10 IIS 8

    Pokud používáte Internet Explorer 10 ve Windows 7 (IIS 7.5), metoda přenosu je iframe.

    Konzola IE 10, IIS 7.5

    Ve Firefoxu nainstalujte doplněk Firebug, abyste získali okno Konzola. Pokud používáte Firefox 19 na Windows 8 (IIS 8), metoda přenosu je WebSockets.

    Firefox 19 IIS 8 Websockets

    Pokud používáte Firefox 19 ve Windows 7 (IIS 7.5), metoda přenosu jsou události odeslané serverem.

    Firefox 19 IIS 7.5 Console

Instalace a kontrola celé ukázky StockTicker

Aplikace StockTicker nainstalovaná balíčkem NuGet Microsoft.AspNet.SignalR.Sample obsahuje více funkcí než zjednodušená verze, kterou jste právě vytvořili od začátku. V této části kurzu nainstalujete balíček NuGet a zkontrolujete nové funkce a kód, který je implementuje.

Instalace balíčku NuGet SignalR.Sample

  1. V Průzkumník řešení klikněte pravým tlačítkem na projekt a klikněte na Spravovat balíčky NuGet.

  2. V dialogovém okně Spravovat balíčky NuGet klikněte na Online, do pole Hledat online zadejte SignalR.Sample a v balíčku SignalR.Sample klikněte na Nainstalovat.

    Instalace balíčku SignalR.Sample

  3. V souboru Global.asax zakomentujte RouteTable.Routes.MapHubs(); , který jste přidali dříve v metodě Application_Start.

    Kód v Souboru Global.asax už není potřeba, protože balíček SignalR.Sample registruje trasu SignalR v souboru App_Start/RegisterHubs.cs :

    [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();
            }
        }
    }
    

    Třída WebActivator, na kterou odkazuje atribut sestavení, je součástí balíčku NuGet WebActivatorEx, který je nainstalován jako závislost balíčku SignalR.Sample.

  4. V Průzkumník řešení rozbalte složku SignalR.Sample vytvořenou instalací balíčku SignalR.Sample.

  5. Ve složce SignalR.Sample klikněte pravým tlačítkem na StockTicker.htmla potom klikněte na Nastavit jako úvodní stránku.

    Poznámka

    Instalace balíčku SignalR.Sample NuGet může změnit verzi jQuery, kterou máte ve složce Scripts . Nový StockTicker.html soubor, který balíček nainstaluje do složky SignalR.Sample , bude synchronizovaný s verzí jQuery, kterou balíček nainstaluje, ale pokud chcete původní StockTicker.html soubor spustit znovu, možná budete muset napřed aktualizovat odkaz jQuery ve značce script.

Spuštění aplikace

  1. Stisknutím klávesy F5 spusťte aplikaci.

    Kromě mřížky, kterou jste viděli dříve, se v aplikaci s úplnými burzovními akciemi zobrazuje vodorovně posuvné okno, ve které se zobrazují stejná data akcií. Při prvním spuštění aplikace se "trh" zavře a zobrazí se statická mřížka a okno s tickerem, které se neposouvá.

    Začátek obrazovky StockTicker

    Když kliknete na Otevřít trh, pole Live Stock Ticker se začne vodorovně posouvat a server začne pravidelně náhodně vysílat změny cen akcií. Při každé změně ceny akcií se aktualizuje mřížka Live Stock Table i pole Live Stock Ticker . Když je změna ceny akcie kladná, akcie se zobrazí se zeleným pozadím, a když je změna záporná, zobrazí se akcie červeným pozadím.

    Aplikace StockTicker, otevřený trh

    Tlačítko Zavřít trh zastaví změny a zastaví posouvání tickeru a tlačítko Reset resetuje všechna data o akciích do počátečního stavu před zahájením změn cen. Pokud otevřete více oken prohlížeče a přejdete na stejnou adresu URL, uvidíte stejná data dynamicky aktualizovaná ve stejnou dobu v každém prohlížeči. Po kliknutí na jedno z tlačítek budou všechny prohlížeče reagovat stejným způsobem ve stejnou dobu.

Zobrazení live stock tickeru

Zobrazení live stock ticker je neuspořádaný seznam v elementu div, který je formátován do jednoho řádku pomocí stylů CSS. Ticker se inicializuje a aktualizuje stejným způsobem jako tabulka: nahrazením zástupných symbolů v <řetězci šablony li> a dynamickým přidáním <prvků li> do elementu <ul> . Posouvání se provádí pomocí funkce jQuery animate, která mění levý okraj neuspořádaného seznamu v rámci div.

Kód HTML burzovního kódu:

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

Šablony stylů CSS s akciemi:

#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;
    }

Kód jQuery, který umožňuje posouvání:

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

Další metody na serveru, které může klient volat

Třída StockTickerHub definuje čtyři další metody, které může klient volat:

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

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

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

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

OpenMarket, CloseMarket a Reset se volají v reakci na tlačítka v horní části stránky. Demonstrují vzor jednoho klienta, který aktivuje změnu stavu, která se okamžitě rozšíří na všechny klienty. Každá z těchto metod volá metodu v StockTicker třídy, která ovlivňuje změnu stavu trhu a poté vysílá nový stav.

Ve třídě StockTicker je stav trhu udržován vlastností MarketState, která vrací hodnotu výčtu MarketState:

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

public enum MarketState
{
    Closed,
    Open
}

Každá z metod, které mění stav trhu, tak učinit uvnitř blok zámku, protože třída StockTicker musí být threadsafe:

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();
    }
}

Aby se zajistilo, že je tento kód bezpečný, _marketState pole, které zajišťuje vlastnost MarketState, je označeno jako nestálé,

private volatile MarketState _marketState;

Metody BroadcastMarketStateChange a BroadcastMarketReset se podobají metodě BroadcastStockPrice, kterou jste již viděli, s tím rozdílem, že volají různé metody definované v klientovi:

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();
}

Další funkce na klientovi, které může server volat

Funkce updateStockPrice teď zpracovává mřížku i zobrazení nabídky a používá jQuery.Color k blikající červené a zelené barvě.

Nové funkce v SignalR.StockTicker.js tlačítka povolit a zakázat podle stavu trhu a zastaví nebo spustí vodorovné posouvání okna tickeru. Vzhledem k tomu, že se do ticker.client přidává více funkcí, použije se k jejich přidání rozšiřující funkce 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();
    }
});

Další nastavení klienta po navázání připojení

Jakmile klient naváže připojení, má ještě další práci: zjistit, jestli je trh otevřený nebo uzavřený, aby mohl volat příslušnou funkci marketOpened nebo marketClosed, a připojit volání metody serveru k tlačítkům.

$.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();
        });
    });

Metody serveru nejsou připojeny k tlačítkům, dokud není navázáno připojení, takže kód se nemůže pokusit volat metody serveru dříve, než budou k dispozici.

Další kroky

V tomto kurzu jste se naučili naprogramovat aplikaci SignalR, která vysílá zprávy ze serveru všem připojeným klientům, a to jak v pravidelných intervalech, tak v reakci na oznámení od libovolného klienta. Vzor použití singleton instance s více vlákny k udržení stavu serveru lze také použít ve scénářích online her pro více hráčů. Příklad najdete ve hře ShootR, která je založená na SignalR.

Kurzy, které ukazují scénáře komunikace mezi dvěma účastníky, najdete v tématech Začínáme se službou SignalR a Aktualizace v reálném čase pomocí SignalR.

Další pokročilé koncepty vývoje služby SignalR najdete na následujících webech se zdrojovým kódem a zdroji informací o službě SignalR: