Samouczek: emisja serwera za pomocą usługi SignalR 2

Ostrzeżenie

Ta dokumentacja nie jest przeznaczona dla najnowszej wersji usługi SignalR. Przyjrzyj się ASP.NET Core SignalR.

W tym samouczku pokazano, jak utworzyć aplikację internetową używającą ASP.NET SignalR 2 w celu zapewnienia funkcjonalności emisji serwera. Emisja serwera oznacza, że serwer uruchamia komunikację wysłaną do klientów.

Aplikacja, którą utworzysz w tym samouczku, symuluje znacznik akcji, typowy scenariusz funkcji emisji serwera. Okresowo serwer losowo aktualizuje ceny akcji i emituje aktualizacje do wszystkich połączonych klientów. W przeglądarce liczby i symbole w kolumnach Zmień i % dynamicznie zmieniają się w odpowiedzi na powiadomienia z serwera. Jeśli otworzysz dodatkowe przeglądarki pod tym samym adresem URL, wszystkie będą wyświetlać te same dane i te same zmiany w danych jednocześnie.

Zrzut ekranu przedstawiający jednoczesne wyświetlanie tych samych zaktualizowanych danych przez wiele przeglądarek internetowych.

W tym samouczku zostały wykonane następujące czynności:

  • Tworzenie projektu
  • Konfigurowanie kodu serwera
  • Sprawdzanie kodu serwera
  • Konfigurowanie kodu klienta
  • Sprawdzanie kodu klienta
  • Testowanie aplikacji
  • Włącz rejestrowanie

Ważne

Jeśli nie chcesz wykonywać kroków tworzenia aplikacji, możesz zainstalować pakiet SignalR.Sample w nowym projekcie Pusta aplikacja internetowa ASP.NET. Jeśli zainstalujesz pakiet NuGet bez wykonywania kroków opisanych w tym samouczku, musisz postępować zgodnie z instrukcjami w pliku readme.txt . Aby uruchomić pakiet, należy dodać klasę uruchamiania OWIN, która wywołuje ConfigureSignalR metodę w zainstalowanym pakiecie. Jeśli nie dodasz klasy uruchamiania OWIN, zostanie wyświetlony błąd. Zobacz sekcję Instalowanie przykładu StockTicker w tym artykule.

Wymagania wstępne

  • Program Visual Studio 2017 z pakietem roboczym Tworzenie aplikacji na platformie ASP.NET i aplikacji internetowych.

Tworzenie projektu

W tej sekcji pokazano, jak za pomocą programu Visual Studio 2017 utworzyć pustą aplikację internetową ASP.NET.

  1. W programie Visual Studio utwórz aplikację internetową ASP.NET.

    Zrzut ekranu przedstawiający sposób tworzenia aplikacji internetowej ASP.NET.

  2. W oknie Nowa aplikacja internetowa ASP.NET — SignalR.StockTicker pozostaw pole Puste i wybierz przycisk OK.

Konfigurowanie kodu serwera

W tej sekcji skonfigurujesz kod uruchamiany na serwerze.

Tworzenie klasy Stock

Zacznij od utworzenia klasy modelu Stock , która będzie używana do przechowywania i przesyłania informacji o magazynie.

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz pozycję Dodaj>klasę.

  2. Nadaj klasie nazwę Stock i dodaj ją do projektu.

  3. Zastąp kod w pliku Stock.cs tym kodem:

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

    Dwie właściwości ustawione podczas tworzenia zapasów to Symbol (na przykład MSFT dla firmy Microsoft) i Price. Inne właściwości zależą od tego, jak i po ustawieniu .Price Przy pierwszym ustawieniu Priceparametru wartość zostanie rozpropagowana na DayOpenwartość . Następnie po ustawieniu Priceaplikacja oblicza Change wartości właściwości i PercentChange na podstawie różnicy między wartościami Price i DayOpen.

Tworzenie klas StockTickerHub i StockTicker

Użyjesz interfejsu API usługi SignalR Hub do obsługi interakcji między serwerem a klientem. Klasa pochodząca StockTickerHub z klasy SignalR Hub będzie obsługiwać odbieranie połączeń i wywołań metod od klientów. Należy również zachować dane zapasów i uruchomić Timer obiekt. Timer Obiekt będzie okresowo wyzwalać aktualizacje cen niezależnie od połączeń klienckich. Nie można umieścić tych funkcji w Hub klasie, ponieważ centra są przejściowe. Aplikacja tworzy Hub wystąpienie klasy dla każdego zadania w centrum, takie jak połączenia i wywołania od klienta do serwera. Dlatego mechanizm, który przechowuje dane akcji, aktualizuje ceny i emituje aktualizacje cen, musi działać w oddzielnej klasie. Nadasz nazwę klasie StockTicker.

Emisja z StockTicker

Chcesz, aby na serwerze było uruchamiane tylko jedno wystąpienie StockTicker klasy, dlatego należy skonfigurować odwołanie z każdego StockTickerHub wystąpienia do wystąpienia pojedynczego StockTicker . Klasa StockTicker musi być emitowana do klientów, ponieważ ma dane zapasów i wyzwala aktualizacje, ale StockTicker nie jest klasą Hub . Klasa StockTicker musi uzyskać odwołanie do obiektu kontekstu połączenia usługi SignalR Hub. Następnie może użyć obiektu kontekstu połączenia SignalR do emisji do klientów.

Utwórz plik StockTickerHub.cs

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz pozycję Dodaj>nowy element.

  2. W obszarze Dodaj nowy element — SignalR.StockTicker wybierz pozycję Zainstalowane>visual C#>Web>SignalR , a następnie wybierz pozycję SignalR Hub Class (v2).

  3. Nadaj klasie nazwę StockTickerHub i dodaj ją do projektu.

    Ten krok tworzy plik klasy StockTickerHub.cs . Jednocześnie dodaje zestaw plików skryptów i odwołań do zestawów obsługujących usługę SignalR do projektu.

  4. Zastąp kod w pliku StockTickerHub.cs tym kodem:

    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. Zapisz plik.

Aplikacja używa klasy Hub do definiowania metod, które klienci mogą wywoływać na serwerze. Definiujesz jedną metodę: GetAllStocks(). Gdy klient początkowo łączy się z serwerem, wywoła tę metodę, aby uzyskać listę wszystkich zapasów z bieżącymi cenami. Metoda może być uruchamiana synchronicznie i zwracana IEnumerable<Stock> , ponieważ zwraca dane z pamięci.

Jeśli metoda musiała pobrać dane, wykonując coś, co wiązałoby się z oczekiwaniem, takim jak wyszukiwanie bazy danych lub wywołanie usługi internetowej, należy określić Task<IEnumerable<Stock>> jako wartość zwracaną w celu włączenia przetwarzania asynchronicznego. Aby uzyskać więcej informacji, zobacz przewodnik interfejsu API usługi ASP.NET SignalR Hubs — Serwer — kiedy należy wykonać asynchronicznie.

Atrybut HubName określa sposób, w jaki aplikacja odwołuje się do centrum w kodzie JavaScript na kliencie. Domyślna nazwa klienta, jeśli nie używasz tego atrybutu, jest wersją camelCase nazwy klasy, która w tym przypadku to .stockTickerHub

Jak zobaczysz później podczas tworzenia StockTicker klasy, aplikacja tworzy pojedyncze wystąpienie tej klasy we właściwości statycznej Instance . Jednotonowe wystąpienie programu StockTicker jest w pamięci niezależnie od liczby klientów, którzy nawiązują połączenie lub rozłączą. To wystąpienie jest używane przez metodę GetAllStocks() do zwracania bieżących informacji o zapasach.

Tworzenie pliku StockTicker.cs

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz pozycję Dodaj>klasę.

  2. Nadaj klasie nazwę StockTicker i dodaj ją do projektu.

  3. Zastąp kod w pliku StockTicker.cs tym kodem:

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

Ponieważ wszystkie wątki będą uruchamiać to samo wystąpienie kodu StockTicker, klasa StockTicker musi być bezpieczna wątkowo.

Sprawdzanie kodu serwera

Jeśli sprawdzisz kod serwera, pomoże Ci to zrozumieć, jak działa aplikacja.

Przechowywanie pojedynczego wystąpienia w polu statycznym

Kod inicjuje pole statyczne _instance , które zastępuje Instance właściwość wystąpieniem klasy. Ponieważ konstruktor jest prywatny, jest to jedyne wystąpienie klasy, którą może utworzyć aplikacja. Aplikacja używa inicjowania z opóźnieniem_instance dla pola. Nie jest to ze względów wydajności. Upewnij się, że tworzenie wystąpienia jest bezpieczne wątkowo.

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

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

Za każdym razem, gdy klient łączy się z serwerem, nowe wystąpienie klasy StockTickerHub uruchomione w osobnym wątku pobiera pojedyncze wystąpienie StockTicker z StockTicker.Instance właściwości statycznej, jak pokazano wcześniej w StockTickerHub klasie.

Przechowywanie danych zapasów w obiekcie ConcurrentDictionary

Konstruktor inicjuje _stocks kolekcję przy użyciu przykładowych danych zapasów i GetAllStocks zwraca zapasy. Jak pokazano wcześniej, ta kolekcja zapasów jest zwracana przez StockTickerHub.GetAllStocksmetodę serwera w Hub klasie, którą klienci mogą wywołać.

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

Kolekcja zapasów jest definiowana jako typ ConcurrentDictionary dla bezpieczeństwa wątków. Alternatywnie można użyć obiektu Słownik i jawnie zablokować słownik podczas wprowadzania w nim zmian.

W przypadku tej przykładowej aplikacji można przechowywać dane aplikacji w pamięci i tracić dane po usunięciu StockTicker wystąpienia przez aplikację. W prawdziwej aplikacji będziesz pracować z magazynem danych zaplecza, takimi jak baza danych.

Okresowe aktualizowanie cen akcji

Konstruktor uruchamia Timer obiekt, który okresowo wywołuje metody aktualizacji cen akcji losowo.

_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 wywołuje metodę UpdateStockPrices, która przekazuje wartość null w parametrze stanu. Przed zaktualizowaniem cen aplikacja przyjmuje blokadę _updateStockPricesLock obiektu. Kod sprawdza, czy inny wątek już aktualizuje ceny, a następnie wywołuje TryUpdateStockPrice poszczególne akcje na liście. Metoda TryUpdateStockPrice decyduje, czy zmienić cenę akcji i ile należy zmienić. Jeśli cena akcji ulegnie zmianie, aplikacja wywołuje wywołanie BroadcastStockPrice emisji zmiany ceny akcji na wszystkich połączonych klientów.

Flaga oznaczona jako nietrwała_updatingStockPrices, aby upewnić się, że jest bezpieczna wątkowo.

private volatile bool _updatingStockPrices = false;

W rzeczywistej aplikacji metoda wywoła usługę internetową, TryUpdateStockPrice aby wyszukać cenę. W tym kodzie aplikacja używa generatora liczb losowych do losowego wprowadzania zmian.

Pobieranie kontekstu usługi SignalR w taki sposób, aby klasa StockTicker mogła być emitowana do klientów

Ponieważ cena zmienia się tutaj w StockTicker obiekcie, jest to obiekt, który musi wywołać metodę updateStockPrice na wszystkich połączonych klientach. Hub W klasie masz interfejs API do wywoływania metod klienta, ale StockTicker nie pochodzi z Hub klasy i nie ma odwołania do żadnego Hub obiektu. Aby rozgłaszać połączonych klientów, StockTicker klasa musi pobrać wystąpienie kontekstu usługi SignalR dla StockTickerHub klasy i użyć jej do wywołania metod na klientach.

Kod pobiera odwołanie do kontekstu usługi SignalR podczas tworzenia wystąpienia klasy pojedynczej, przekazuje to odwołanie do konstruktora, a konstruktor umieszcza go we Clients właściwości.

Istnieją dwa powody, dla których chcesz pobrać kontekst tylko raz: pobieranie kontekstu jest kosztownym zadaniem, a pobranie go po upewnieniu się, że aplikacja zachowuje docelową kolejność komunikatów wysyłanych do klientów.

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 Pobranie właściwości kontekstu i umieszczenie go we StockTickerClient właściwości umożliwia napisanie kodu w celu wywołania metod klienta, które wyglądają tak samo, jak w Hub klasie. Na przykład w celu emisji do wszystkich klientów można napisać Clients.All.updateStockPrice(stock)polecenie .

Wywoływana updateStockPriceBroadcastStockPrice metoda jeszcze nie istnieje. Później dodasz go podczas pisania kodu uruchamianego na kliencie. Tutaj można odwoływać się, updateStockPrice ponieważ Clients.All jest dynamiczny, co oznacza, że aplikacja będzie oceniać wyrażenie w czasie wykonywania. Po wykonaniu tego wywołania metody usługa SignalR wyśle nazwę metody i wartość parametru do klienta, a jeśli klient ma metodę o nazwie updateStockPrice, aplikacja wywoła tę metodę i przekaże do niej wartość parametru.

Clients.All oznacza wysyłanie do wszystkich klientów. Usługa SignalR udostępnia inne opcje określania, do których klientów lub grup klientów mają być wysyłane. Aby uzyskać więcej informacji, zobacz HubConnectionContext.

Rejestrowanie trasy usługi SignalR

Serwer musi wiedzieć, który adres URL przechwytuje i kieruje do usługi SignalR. Aby to zrobić, dodaj klasę uruchamiania OWIN:

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz pozycję Dodaj>nowy element.

  2. W obszarze Dodaj nowy element — SignalR.StockTicker wybierz pozycję Zainstalowane>visual C#>Web , a następnie wybierz pozycję Klasa uruchamiania OWIN.

  3. Nadaj klasie nazwę Startup i wybierz przycisk OK.

  4. Zastąp kod domyślny w pliku Startup.cs tym kodem:

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

Zakończono konfigurowanie kodu serwera. W następnej sekcji skonfigurujesz klienta.

Konfigurowanie kodu klienta

W tej sekcji skonfigurujesz kod uruchamiany na kliencie.

Tworzenie strony HTML i pliku JavaScript

Na stronie HTML zostaną wyświetlone dane, a plik JavaScript będzie organizować dane.

Tworzenie StockTicker.html

Najpierw dodasz klienta HTML.

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz pozycję Dodaj>stronę HTML.

  2. Nadaj plikowi nazwę StockTicker i wybierz przycisk OK.

  3. Zastąp kod domyślny w pliku StockTicker.html tym kodem:

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

    Kod HTML tworzy tabelę z pięcioma kolumnami, wierszem nagłówka i wierszem danych z pojedynczą komórką, która obejmuje wszystkie pięć kolumn. Wiersz danych pokazuje "ładowanie..." chwilowo po uruchomieniu aplikacji. Kod JavaScript usunie ten wiersz i doda wiersze w miejscu z danymi magazynowymi pobranymi z serwera.

    Tagi skryptu określają:

    • Plik skryptu jQuery.

    • Plik skryptu podstawowego usługi SignalR.

    • Plik skryptu serwera proxy usługi SignalR.

    • Plik skryptu StockTicker, który zostanie utworzony później.

    Aplikacja dynamicznie generuje plik skryptu serwera proxy usługi SignalR. Określa adres URL "/signalr/hubs" i definiuje metody serwera proxy dla metod w klasie Hub, w tym przypadku dla StockTickerHub.GetAllStockselementu . Jeśli wolisz, możesz ręcznie wygenerować ten plik JavaScript przy użyciu narzędzi SignalR Utilities. Nie zapomnij wyłączyć dynamicznego tworzenia plików w wywołaniu MapHubs metody.

  4. W Eksplorator rozwiązań rozwiń węzeł Skrypty.

    Biblioteki skryptów dla bibliotek jQuery i SignalR są widoczne w projekcie.

    Ważne

    Menedżer pakietów zainstaluje późniejszą wersję skryptów usługi SignalR.

  5. Zaktualizuj odwołania skryptu w bloku kodu, aby odpowiadały wersjom plików skryptów w projekcie.

  6. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy StockTicker.html, a następnie wybierz pozycję Ustaw jako stronę startową.

Tworzenie StockTicker.js

Teraz utwórz plik JavaScript.

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz polecenie Dodaj>plik JavaScript.

  2. Nadaj plikowi nazwę StockTicker i wybierz przycisk OK.

  3. Dodaj ten kod do pliku 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);
    
    });
    

Sprawdzanie kodu klienta

Jeśli sprawdzisz kod klienta, pomoże ci dowiedzieć się, jak kod klienta współdziała z kodem serwera, aby aplikacja działała.

Uruchamianie połączenia

$.connection odnosi się do serwerów proxy usługi SignalR. Kod pobiera odwołanie do serwera proxy klasy StockTickerHub i umieszcza go w zmiennej ticker . Nazwa serwera proxy to nazwa, która została ustawiona HubName przez atrybut:

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

Po zdefiniowaniu wszystkich zmiennych i funkcji ostatni wiersz kodu w pliku inicjuje połączenie usługi SignalR przez wywołanie funkcji SignalR start . Funkcja start jest wykonywana asynchronicznie i zwraca obiekt odroczony jQuery. Możesz wywołać funkcję done, aby określić funkcję do wywołania po zakończeniu akcji asynchronicznej przez aplikację.

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

Pobieranie wszystkich zapasów

Funkcja init wywołuje getAllStocks funkcję na serwerze i używa informacji zwracanych przez serwer do zaktualizowania tabeli magazynowej. Zwróć uwagę, że domyślnie na kliencie trzeba używać biblioteki camelCasing, mimo że nazwa metody jest przypadek pascala na serwerze. Reguła camelCasing dotyczy tylko metod, a nie obiektów. Na przykład odwołujesz się do stock.Symbol elementu i stock.Price, a nie stock.symbol lub 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();
}

W metodzie init aplikacja tworzy kod HTML dla wiersza tabeli dla każdego obiektu stockowego odebranego z serwera przez wywołanie metody formatowania właściwości stock obiektu, a następnie przez wywołanie wywołania formatStock w supplant celu zastąpienia symboli zastępczych w zmiennej rowTemplate wartościami stock właściwości obiektu. Wynikowy kod HTML jest następnie dołączany do tabeli zapasów.

Uwaga

Wywołaj metodę init , przekazując ją jako funkcję wykonywaną callback po zakończeniu funkcji asynchronicznej start . Jeśli wywołasz oddzielną instrukcję Języka JavaScript po wywołaniu initstartmetody , funkcja zakończy się niepowodzeniem, ponieważ zostanie uruchomiona natychmiast bez oczekiwania na zakończenie nawiązywania połączenia przez funkcję uruchamiania. W takim przypadku init funkcja spróbuje wywołać getAllStocks funkcję przed nawiązaniem połączenia z serwerem przez aplikację.

Pobieranie zaktualizowanych cen akcji

Gdy serwer zmieni cenę akcji, wywołuje updateStockPrice on połączonych klientów. Aplikacja dodaje funkcję do właściwości stockTicker klienta serwera proxy, aby udostępnić ją wywołaniom z serwera.

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

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

Funkcja updateStockPrice formatuje obiekt stockowy odebrany z serwera do wiersza tabeli w taki sam sposób jak w init funkcji. Zamiast dołączać wiersz do tabeli, znajduje bieżący wiersz zapasów w tabeli i zastępuje ten wiersz nowym wierszem.

Testowanie aplikacji

Możesz przetestować aplikację, aby upewnić się, że działa. Zobaczysz, że wszystkie okna przeglądarki wyświetlają tabelę akcji na żywo ze wahaniami cen akcji.

  1. Na pasku narzędzi włącz debugowanie skryptu , a następnie wybierz przycisk odtwarzania, aby uruchomić aplikację w trybie debugowania.

    Zrzut ekranu przedstawiający użytkownika, który włącza tryb debugowania i wybiera odtwarzanie.

    Zostanie otwarte okno przeglądarki z wyświetloną tabelą Live Stock. Tabela giełdowa początkowo pokazuje "ładowanie..." wiersz, a następnie, po krótkim czasie, aplikacja wyświetla początkowe dane zapasów, a następnie ceny akcji zaczynają się zmieniać.

  2. Skopiuj adres URL z przeglądarki, otwórz dwie inne przeglądarki i wklej adresy URL do pasków adresów.

    Początkowy ekran zapasów jest taki sam jak pierwsza przeglądarka i zmiany odbywają się jednocześnie.

  3. Zamknij wszystkie przeglądarki, otwórz nową przeglądarkę i przejdź do tego samego adresu URL.

    Obiekt singleton StockTicker nadal działa na serwerze. Live Stock Table pokazuje, że zapasy nadal się zmieniają. Nie widzisz początkowej tabeli z zerowymi liczbami zmian.

  4. Zamknij okno przeglądarki.

Włącz rejestrowanie

Usługa SignalR ma wbudowaną funkcję rejestrowania, którą można włączyć na kliencie, aby ułatwić rozwiązywanie problemów. W tej sekcji włączysz rejestrowanie i zobaczysz przykłady pokazujące, jak dzienniki informują, które z następujących metod transportu usługi SignalR używa:

W przypadku dowolnego połączenia usługa SignalR wybiera najlepszą metodę transportu, którą obsługuje zarówno serwer, jak i klient.

  1. Otwórz StockTicker.js.

  2. Dodaj ten wyróżniony wiersz kodu, aby włączyć rejestrowanie bezpośrednio przed kodem, który inicjuje połączenie na końcu pliku:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  3. Naciśnij klawisz F5 , aby uruchomić projekt.

  4. Otwórz okno narzędzi deweloperskich przeglądarki i wybierz konsolę, aby wyświetlić dzienniki. Może być konieczne odświeżenie strony, aby wyświetlić dzienniki usługi SignalR negocjujące metodę transportu dla nowego połączenia.

    • Jeśli korzystasz z programu Internet Explorer 10 w Windows 8 (IIS 8), metoda transportu to WebSockets.

    • Jeśli korzystasz z programu Internet Explorer 10 w systemie Windows 7 (IIS 7.5), metoda transportu to iframe.

    • Jeśli używasz przeglądarki Firefox 19 w Windows 8 (IIS 8), metoda transportu to WebSockets.

      Porada

      W przeglądarce Firefox zainstaluj dodatek Firebug, aby uzyskać okno Konsoli.

    • Jeśli używasz przeglądarki Firefox 19 w systemie Windows 7 (IIS 7.5), metoda transportu to zdarzenia wysyłane przez serwer .

Instalowanie przykładu StockTicker

Aplikacja Microsoft.AspNet.SignalR.Sample instaluje aplikację StockTicker. Pakiet NuGet zawiera więcej funkcji niż uproszczona wersja utworzona od podstaw. W tej sekcji samouczka zainstalujesz pakiet NuGet i zapoznasz się z nowymi funkcjami oraz kodem, który je implementuje.

Ważne

Jeśli zainstalujesz pakiet bez wykonywania wcześniejszych kroków tego samouczka, musisz dodać klasę uruchamiania OWIN do projektu. Ten readme.txt plik pakietu NuGet wyjaśnia ten krok.

Instalowanie pakietu NuGet SignalR.Sample

  1. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz polecenie Zarządzaj pakietami NuGet.

  2. W Menedżerze pakietów NuGet: SignalR.StockTicker wybierz pozycję Przeglądaj.

  3. W obszarze Źródło pakietu wybierz pozycję nuget.org.

  4. Wprowadź ciąg SignalR.Sample w polu wyszukiwania i wybierz pozycję Microsoft.AspNet.SignalR.Sample>Install.

  5. W Eksplorator rozwiązań rozwiń folder SignalR.Sample.

    Zainstalowanie pakietu SignalR.Sample spowodowało utworzenie folderu i jego zawartości.

  6. W folderze SignalR.Sample kliknij prawym przyciskiem myszy StockTicker.html, a następnie wybierz pozycję Ustaw jako stronę startową.

    Uwaga

    Zainstalowanie pakietu NuGet SignalR.Sample może zmienić wersję biblioteki jQuery, którą masz w folderze Scripts . Nowy plik StockTicker.html instalowany w folderze SignalR.Sample będzie zsynchronizowany z zainstalowaną wersją pakietu jQuery, ale jeśli chcesz ponownie uruchomić oryginalny plik StockTicker.html , może być konieczne zaktualizowanie odwołania jQuery w tagu skryptu.

Uruchamianie aplikacji

Tabela, która była wyświetlona w pierwszej aplikacji, miała przydatne funkcje. Pełna aplikacja znacznika zapasów pokazuje nowe funkcje: okno przewijania w poziomie, które pokazuje dane zapasów i zapasy, które zmieniają kolor w miarę wzrostu i upadku.

  1. Naciśnij klawisz F5 , aby uruchomić aplikację.

    Po pierwszym uruchomieniu aplikacji "rynek" jest "zamknięty" i zobaczysz tabelę statyczną i okno znacznika, które nie jest przewijane.

  2. Wybierz pozycję Otwórz rynek.

    Zrzut ekranu przedstawiający znacznik na żywo.

    • Pole Live Stock Ticker zaczyna przewijać w poziomie, a serwer zaczyna okresowo emitować zmiany cen akcji na podstawie losowych.

    • Za każdym razem, gdy zmienia się cena akcji, aplikacja aktualizuje zarówno tabelę live stock , jak i live stock ticker.

    • Gdy zmiana cen akcji jest dodatnia, aplikacja pokazuje akcje z zielonym tłem.

    • Gdy zmiana jest ujemna, aplikacja wyświetla zapasy z czerwonym tłem.

  3. Wybierz pozycję Zamknij rynek.

    • Aktualizacja tabeli zostanie zatrzymana.

    • Znacznik przestaje przewijać.

  4. Wybierz pozycję Resetuj.

    • Wszystkie dane zapasów są resetowane.

    • Aplikacja przywraca stan początkowy przed rozpoczęciem zmian cen.

  5. Skopiuj adres URL z przeglądarki, otwórz dwie inne przeglądarki i wklej adresy URL do pasków adresów.

  6. Te same dane są dynamicznie aktualizowane w każdej przeglądarce.

  7. Po wybraniu dowolnej kontrolki wszystkie przeglądarki odpowiadają w ten sam sposób w tym samym czasie.

Wyświetlacz Live Stock Ticker

Wyświetlacz Live Stock Ticker jest nieuporządkowaną listą w <div> elemecie sformatowanym w jednym wierszu według stylów CSS. Aplikacja inicjuje i aktualizuje znacznik tak samo jak tabela: zastępując symbole zastępcze w <li> ciągu szablonu i dynamicznie dodając <li> elementy do <ul> elementu. Aplikacja zawiera przewijanie przy użyciu funkcji jQuery animate , aby zmienić lewy margines listy nieurządkowanej w obrębie <div>.

StockTicker.html SignalR.Sample

Kod HTML znacznika zapasów:

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

SignalR.Sample StockTicker.css

Kod CSS znacznika zapasów:

#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.StockTicker.js SignalR.Sample

Kod jQuery, który sprawia, że przewija:

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

Dodatkowe metody na serwerze, które klient może wywołać

Aby zwiększyć elastyczność aplikacji, istnieją dodatkowe metody, które aplikacja może wywołać.

SignalR.Sample StockTickerHub.cs

Klasa StockTickerHub definiuje cztery dodatkowe metody, które klient może wywołać:

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

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

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

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

Aplikacja wywołuje OpenMarketprzyciski , CloseMarketi Reset w odpowiedzi na przyciski w górnej części strony. Demonstrują wzorzec jednego klienta wyzwalającego zmianę stanu natychmiast propagowanego do wszystkich klientów. Każda z tych metod wywołuje metodę w StockTicker klasie, która powoduje zmianę stanu rynku, a następnie emituje nowy stan.

SignalR.Sample StockTicker.cs

StockTicker W klasie aplikacja zachowuje stan rynku z właściwością zwracającą MarketStateMarketState wartość wyliczenia:

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

public enum MarketState
{
    Closed,
    Open
}

Każda z metod zmieniających stan rynku robi to wewnątrz bloku blokady, ponieważ StockTicker klasa musi być bezpieczna wątkowo:

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 upewnić się, że ten kod jest bezpieczny wątkowo, _marketState pole, które popiera właściwość oznaczoną MarketStatevolatilejako :

private volatile MarketState _marketState;

Metody BroadcastMarketStateChange i BroadcastMarketReset są podobne do metody BroadcastStockPrice, która została już wyświetlona, z wyjątkiem wywoływania różnych metod zdefiniowanych na kliencie:

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

Dodatkowe funkcje na kliencie, które serwer może wywołać

Funkcja updateStockPrice obsługuje teraz zarówno tabelę, jak i wyświetlacz znacznika, a następnie używa jQuery.Color jej do migania kolorów czerwonych i zielonych.

Nowe funkcje w SignalR.StockTicker.js włączać i wyłączać przyciski na podstawie stanu rynku. Zatrzymują się również lub uruchamiają przewijanie poziome Live Stock Ticker . Ponieważ wiele funkcji jest dodawanych do ticker.clientprogramu , aplikacja używa funkcji rozszerzenia jQuery , aby je dodać.

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

Dodatkowa konfiguracja klienta po nawiązaniu połączenia

Po nawiązaniu połączenia przez klienta należy wykonać dodatkową pracę:

  • Dowiedz się, czy rynek jest otwarty lub zamknięty, aby wywołać odpowiednią marketOpened lub marketClosed funkcję.

  • Dołącz wywołania metody serwera do przycisków.

$.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 serwera nie są przewodowe do przycisków dopiero po ustanowieniu połączenia przez aplikację. Dzięki temu kod nie może wywołać metod serwera, zanim będą dostępne.

Dodatkowe zasoby

W tym samouczku przedstawiono sposób programowania aplikacji SignalR, która emituje komunikaty z serwera do wszystkich połączonych klientów. Teraz możesz emitować komunikaty okresowo i w odpowiedzi na powiadomienia od dowolnego klienta. Można użyć koncepcji wielowątkowego pojedynczego wystąpienia, aby zachować stan serwera w scenariuszach gry online z wieloma graczami. Aby zapoznać się z przykładem, zobacz grę ShootR opartą na usłudze SignalR.

Aby uzyskać samouczki pokazujące scenariusze komunikacji równorzędnej, zobacz Wprowadzenie z usługą SignalR i aktualizowaniem w czasie rzeczywistym za pomocą usługi SignalR.

Aby uzyskać więcej informacji na temat usługi SignalR, zobacz następujące zasoby:

Następne kroki

W tym samouczku zostały wykonane następujące czynności:

  • Utworzono projekt
  • Konfigurowanie kodu serwera
  • Zbadano kod serwera
  • Konfigurowanie kodu klienta
  • Zbadano kod klienta
  • Testowanie aplikacji
  • Włączone rejestrowanie

Przejdź do następnego artykułu, aby dowiedzieć się, jak utworzyć aplikację internetową czasu rzeczywistego korzystającą z usługi ASP.NET SignalR 2.