Samouczek: emisje serwera z użyciem wzorca ASP.NET SignalR 1.x
Autor: Patrick Fletcher, Tom Dykstra
Ostrzeżenie
Ta dokumentacja nie dotyczy najnowszej wersji usługi SignalR. Przyjrzyj się ASP.NET Core SignalR.
W tym samouczku pokazano, jak utworzyć aplikację internetową korzystającą z usługi ASP.NET SignalR w celu zapewnienia funkcji emisji serwera. Emisja serwera oznacza, że komunikacja wysyłana do klientów jest inicjowana przez serwer. Ten scenariusz wymaga innego podejścia programistycznego niż scenariusze komunikacji równorzędnej, takie jak aplikacje do czatów, w których komunikacja wysyłana do klientów jest inicjowana przez co najmniej jednego klienta.
Aplikacja, którą utworzysz w tym samouczku, symuluje znacznik akcji, typowy scenariusz dla funkcji emisji serwera.
Komentarze do samouczka są mile widziane. Jeśli masz pytania, które nie są bezpośrednio związane z tym samouczkiem, możesz opublikować je na forum ASP.NET SignalR lub StackOverflow.com.
Omówienie
Pakiet NuGet Microsoft.AspNet.SignalR.Sample instaluje przykładową symulowaną aplikację znacznika zapasów w projekcie programu Visual Studio. W pierwszej części tego samouczka utworzysz uproszczoną wersję tej aplikacji od podstaw. W pozostałej części samouczka zainstalujesz pakiet NuGet i zapoznasz się z dodatkowymi funkcjami i kodem tworzonym przez niego.
Aplikacja giełdowa jest reprezentatywna dla rodzaju aplikacji w czasie rzeczywistym, w której chcesz okresowo "wypychać" lub emitować powiadomienia z serwera do wszystkich połączonych klientów.
Aplikacja, którą utworzysz w pierwszej części tego samouczka, wyświetla siatkę z danymi giełdowymi.
Okresowo serwer losowo aktualizuje ceny akcji i wypycha aktualizacje do wszystkich połączonych klientów. W przeglądarce numery 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.
Ten samouczek zawiera następujące sekcje:
- Wymagania wstępne
- Tworzenie projektu
- Dodawanie pakietów NuGet usługi SignalR
- Konfigurowanie kodu serwera
- Konfigurowanie kodu klienta
- Testowanie aplikacji
- Włącz rejestrowanie
- Instalowanie i przeglądanie pełnego przykładu StockTicker
- Następne kroki
Uwaga
Jeśli nie chcesz wykonywać kroków tworzenia aplikacji, możesz zainstalować pakiet SignalR.Sample w nowym projekcie Empty ASP.NET Web Application i przeczytać te kroki, aby uzyskać wyjaśnienia kodu. Pierwsza część samouczka obejmuje podzbiór kodu SignalR.Sample, a druga część zawiera opis kluczowych funkcji dodatkowych funkcji w pakiecie SignalR.Sample.
Wymagania wstępne
Przed rozpoczęciem upewnij się, że na komputerze jest zainstalowany program Visual Studio 2012 lub 2010 SP1. Jeśli nie masz programu Visual Studio, zobacz ASP.NET Pliki do pobrania , aby uzyskać bezpłatny program Visual Studio 2012 Express for Web.
Jeśli masz program Visual Studio 2010, upewnij się, że zainstalowano pakiet NuGet .
Tworzenie projektu
W menu Plik kliknij polecenie Nowy projekt.
W oknie dialogowym Nowy projekt rozwiń węzeł C# w obszarze Szablony i wybierz pozycję Sieć Web.
Wybierz szablon ASP.NET Empty Web Application (Pusta aplikacja internetowa ), nadaj projektowi nazwę SignalR.StockTicker, a następnie kliknij przycisk OK.
Dodawanie pakietów NuGet usługi SignalR
Dodawanie pakietów NuGet SignalR i JQuery
Funkcjonalność usługi SignalR można dodać do projektu, instalując pakiet NuGet.
Kliknij pozycję Narzędzia | Menedżer pakietów NuGet | Konsola menedżera pakietów.
Wprowadź następujące polecenie w menedżerze pakietów.
Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
Pakiet SignalR instaluje wiele innych pakietów NuGet jako zależności. Po zakończeniu instalacji wszystkie składniki serwera i klienta wymagane do korzystania z usługi SignalR w aplikacji ASP.NET.
Konfigurowanie kodu serwera
W tej sekcji skonfigurujesz kod uruchamiany na serwerze.
Tworzenie klasy Stock
Rozpoczniesz od utworzenia klasy modelu Stock, która będzie używana do przechowywania i przesyłania informacji o magazynie.
Utwórz nowy plik klasy w folderze projektu, nadaj mu nazwę Stock.cs, a następnie zastąp kod szablonu następującym 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, które zostaną ustawione podczas tworzenia akcji, to Symbol (na przykład MSFT dla firmy Microsoft) i Cena. Inne właściwości zależą od tego, jak i kiedy ustawisz cenę. Przy pierwszym ustawieniu pozycji Price (Cena) wartość jest propagowana do dayOpen. W kolejnych momentach ustawiania wartości właściwości Change i PercentChange są obliczane na podstawie różnicy między wartościami Price i DayOpen.
Tworzenie klas StockTicker i StockTickerHub
Użyjesz interfejsu API usługi SignalR Hub do obsługi interakcji między serwerami i klientami. Klasa StockTickerHub pochodząca z klasy SignalR Hub będzie obsługiwać odbieranie połączeń i wywołań metod od klientów. Należy również utrzymywać dane akcji i uruchamiać obiekt czasomierza, aby okresowo wyzwalać aktualizacje cen niezależnie od połączeń klientów. Nie można umieścić tych funkcji w klasie Hub, ponieważ wystąpienia centrum są przejściowe. Wystąpienie klasy centrum jest tworzone dla każdej operacji w centrum, takiej jak połączenia i wywołania z klienta do serwera. Dlatego mechanizm, który przechowuje dane akcji, aktualizuje ceny i emituje aktualizacje cen, musi działać w oddzielnej klasie, którą będziesz nazywać StockTicker.
Chcesz, aby na serwerze było uruchamiane tylko jedno wystąpienie klasy StockTicker, dlatego należy skonfigurować odwołanie z każdego wystąpienia StockTickerHub do pojedynczego wystąpienia StockTickerKer. Klasa StockTicker musi być w stanie rozgłaszać do klientów, ponieważ ma dane stockowe i wyzwala aktualizacje, ale StockTicker nie jest klasą Centrum. W związku z tym 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 usługi SignalR do emisji do klientów.
W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i kliknij polecenie Dodaj nowy element.
Jeśli masz program Visual Studio 2012 z aktualizacją ASP.NET and Web Tools 2012.2, kliknij pozycję Sieć Web w obszarze Visual C# i wybierz szablon elementu Klasy usługi SignalR Hub. W przeciwnym razie wybierz szablon Klasa .
Nadaj nowej klasie nazwę StockTickerHub.cs, a następnie kliknij przycisk Dodaj.
Zastąp kod szablonu następującym kodem:
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(); } } }
Klasa Hub służy 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ć wykonywana synchronicznie i zwracanaIEnumerable<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, w jaki sposób centrum będzie przywoływane w kodzie JavaScript na kliencie. Domyślna nazwa klienta, jeśli nie używasz tego atrybutu, jest wielbłądową wersją nazwy klasy, która w tym przypadku byłaby stockTickerHub.
Jak zobaczysz później podczas tworzenia klasy StockTicker, pojedyncze wystąpienie tej klasy jest tworzone w jego statycznej właściwości Instance. To pojedyncze wystąpienie StockTicker pozostaje w pamięci niezależnie od tego, ile klientów łączy lub rozłącza, a to wystąpienie jest używane przez metodę GetAllStocks w celu zwrócenia bieżących informacji o zapasach.
Utwórz nowy plik klasy w folderze projektu, nadaj mu nazwę StockTicker.cs, a następnie zastąp kod szablonu następującym 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 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); } } }
Ponieważ wiele wątków będzie działać w tym samym wystąpieniu kodu StockTicker, klasa StockTicker musi być wątkowa.
Przechowywanie pojedynczego wystąpienia w polu statycznym
Kod inicjuje pole statycznej _instance, które tworzy kopię zapasową właściwości Instance z wystąpieniem klasy, i jest to jedyne wystąpienie klasy, które można utworzyć, ponieważ konstruktor jest oznaczony jako prywatny. Inicjowanie z opóźnieniem jest używane dla pola _instance, a nie ze względu na wydajność, ale w celu zapewnienia, że tworzenie wystąpienia jest wątkowe.
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 uruchomionej w osobnym wątku pobiera pojedyncze wystąpienie StockTicker z właściwości statycznej StockTicker.Instance, jak pokazano wcześniej w klasie StockTickerHub.
Przechowywanie danych zapasów w obiekcie ConcurrentDictionary
Konstruktor inicjuje kolekcję _stocks z przykładowymi danymi zapasów, a getAllStocks zwraca zapasy. Jak pokazano wcześniej, ta kolekcja akcji jest z kolei zwracana przez StockTickerHub.GetAllStocks, która jest metodą serwera w klasie Hub, którą klienci mogą wywołać.
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; }
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 utracić dane, gdy wystąpienie StockTicker zostanie usunięte. W prawdziwej aplikacji będziesz pracować z magazynem danych zaplecza, takim jak baza danych.
Okresowe aktualizowanie cen akcji
Konstruktor uruchamia obiekt czasomierza, który okresowo wywołuje metody aktualizujące ceny 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; }
Wartość UpdateStockPrices jest wywoływana przez czasomierz, który przekazuje wartość null w parametrze stanu. Przed zaktualizowaniem cen blokada jest wykonywana na obiekcie _updateStockPricesLock. Kod sprawdza, czy inny wątek już aktualizuje ceny, a następnie wywołuje wartość TryUpdateStockPrice na każdej giełdzie na liście. Metoda TryUpdateStockPrice decyduje, czy zmienić cenę akcji i ile należy zmienić. Jeśli cena akcji zostanie zmieniona, nazwa BroadcastStockPrice jest wywoływana w celu emisji zmiany ceny akcji na wszystkich połączonych klientów.
Flaga _updatingStockPrices jest oznaczona jako niestabilna , aby zapewnić bezpieczeństwo dostępu do niej.
private volatile bool _updatingStockPrices = false;
W rzeczywistej aplikacji metoda TryUpdateStockPrice wywoła usługę internetową, aby wyszukać cenę; w tym kodzie 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ż zmiany cen pochodzą tutaj w obiekcie StockTicker, jest to obiekt, który musi wywołać metodę updateStockPrice na wszystkich połączonych klientach. W klasie Hub masz interfejs API do wywoływania metod klienta, ale StockTicker nie pochodzi z klasy Hub i nie ma odwołania do żadnego obiektu centrum. W związku z tym w celu emisji do połączonych klientów klasa StockTicker musi uzyskać wystąpienie kontekstu SignalR dla klasy StockTickerHub i używać go do wywoływania 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 właściwości Clients.
Istnieją dwa powody, dla których chcesz pobrać kontekst tylko raz: uzyskanie kontekstu jest kosztowną operacją i uzyskanie go raz gwarantuje, że docelowa kolejność komunikatów wysyłanych do klientów jest zachowana.
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); }
Pobranie właściwości Clients kontekstu i umieszczenie go we właściwości StockTickerClient umożliwia napisanie kodu w celu wywołania metod klienta, które wyglądają tak samo jak w klasie Hub. Na przykład w celu emisji do wszystkich klientów można napisać Clients.All.updateStockPrice(stock).
Metoda updateStockPrice wywoływana w elemecie BroadcastStockPrice jeszcze nie istnieje; Dodasz go później podczas pisania kodu uruchamianego na kliencie. Tutaj można odwoływać się do updateStockPrice, ponieważ Clients.All jest dynamiczny, co oznacza, że wyrażenie będzie oceniane 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, ta metoda zostanie wywołana, a wartość parametru zostanie przekazana do niego.
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. W tym celu dodasz kod do pliku Global.asax .
W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt, a następnie kliknij polecenie Dodaj nowy element.
Wybierz szablon globalny element klasy aplikacji , a następnie kliknij przycisk Dodaj.
Dodaj kod rejestracji trasy usługi SignalR do metody Application_Start:
protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHubs(); }
Domyślnie podstawowy adres URL dla całego ruchu usługi SignalR to "/signalr", a element "/signalr/hubs" służy do pobierania dynamicznie wygenerowanego pliku JavaScript definiującego serwery proxy dla wszystkich centrów, które masz w aplikacji. Metoda MapHubs zawiera przeciążenia, które umożliwiają określenie innego podstawowego adresu URL i niektórych opcji usługi SignalR w wystąpieniu klasy HubConfiguration .
Dodaj instrukcję using w górnej części pliku:
using System.Web.Routing;
Zapisz i zamknij plik Global.asax i skompiluj projekt.
Ukończono konfigurowanie kodu serwera. W następnej sekcji skonfigurujesz klienta.
Konfigurowanie kodu klienta
Utwórz nowy plik HTML w folderze projektu i nadaj mu nazwę StockTicker.html.
Zastąp kod szablonu następującym 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.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>
Kod HTML tworzy tabelę z 5 kolumnami, wierszem nagłówka i wierszem danych z pojedynczą komórką, która obejmuje wszystkie 5 kolumn. Wiersz danych wyświetla komunikat "ładowanie..." i będą wyświetlane tylko 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 i plik skryptu StockTicker, który utworzysz później. Plik skryptu serwera proxy usługi SignalR, który określa adres URL "/signalr/hubs", jest generowany dynamicznie i definiuje metody serwera proxy dla metod w klasie Hub, w tym przypadku dla StockTickerHub.GetAllStocks. Jeśli wolisz, możesz ręcznie wygenerować ten plik JavaScript przy użyciu narzędzi SignalR i wyłączyć dynamiczne tworzenie plików w wywołaniu metody MapHubs.
-
Ważne
Upewnij się, że odwołania do pliku JavaScript w StockTicker.html są poprawne. Oznacza to, że wersja jQuery w tagu skryptu (w przykładzie 1.8.2) jest taka sama jak wersja jQuery w folderze Skrypty projektu i upewnij się, że wersja signalR w tagu skryptu jest taka sama jak wersja signalr w folderze Skrypty projektu. W razie potrzeby zmień nazwy plików w tagach skryptów.
W Eksplorator rozwiązań kliknij prawym przyciskiem myszy StockTicker.html, a następnie kliknij pozycję Ustaw jako stronę startową.
Utwórz nowy plik JavaScript w folderze projektu i nadaj mu nazwę StockTicker.js..
Zastąp kod szablonu następującym kodem:
// 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 odnosi się do serwerów proxy usługi SignalR. Kod pobiera odwołanie do serwera proxy dla klasy StockTickerHub i umieszcza go w zmiennej znacznika. Nazwa serwera proxy to nazwa, która została ustawiona przez atrybut [HubName]:
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 startowej usługi SignalR. Funkcja startowa jest wykonywana asynchronicznie i zwraca obiekt odroczony jQuery, co oznacza, że można wywołać funkcję done, aby określić funkcję do wywołania po zakończeniu operacji asynchronicznej.
$.connection.hub.start().done(init);
Funkcja init wywołuje funkcję getAllStocks na serwerze i używa informacji zwracanych przez serwer w celu zaktualizowania tabeli zapasów. Zauważ, że domyślnie na kliencie trzeba używać wielkości liter wielbłąda, chociaż nazwa metody jest przypadek pascala na serwerze. Reguła wielkości liter wielbłąda dotyczy tylko metod, a nie obiektów. Na przykład odwołujesz się do zapasów. Symbol i zapasy. Price, not 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(); }
Jeśli chcesz użyć casingu pascal na kliencie lub jeśli chcesz użyć zupełnie innej nazwy metody, możesz ozdobić metodę Hub atrybutem HubMethodName w taki sam sposób, jak udekorowano klasę Hub za pomocą atrybutu HubName.
W metodzie init kod HTML dla wiersza tabeli jest tworzony dla każdego obiektu stockowego odebranego z serwera przez wywołanie formatu formatStock w celu formatowania właściwości obiektu stockowego, a następnie przez wywołanie zastąpienia (zdefiniowanego u góry StockTicker.js) w celu zastąpienia symboli zastępczych w zmiennej rowTemplate wartościami właściwości obiektu stockowego. Wynikowy kod HTML jest następnie dołączany do tabeli zapasów.
Wywołaj init, przekazując ją jako funkcję wywołania zwrotnego wykonywaną po zakończeniu asynchronicznej funkcji uruchamiania. Jeśli wywołasz init jako oddzielną instrukcję Języka JavaScript po wywołaniu polecenia Start, funkcja zakończy się niepowodzeniem, ponieważ zostanie wykonana natychmiast bez oczekiwania na zakończenie nawiązywania połączenia przez funkcję uruchamiania. W takim przypadku funkcja init spróbuje wywołać funkcję getAllStocks przed nawiązaniem połączenia z serwerem.
Gdy serwer zmieni cenę akcji, wywołuje wartość updateStockPrice na połączonych klientach. Funkcja jest dodawana do właściwości klienta serwera proxy stockTicker w celu udostępnienia jej do wywołań 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 funkcji init. Jednak zamiast dołączać wiersz do tabeli, znajduje bieżący wiersz zapasów w tabeli i zastępuje ten wiersz nowym wierszem.
Testowanie aplikacji
Naciśnij klawisz F5, aby uruchomić aplikację w trybie debugowania.
Tabela giełdowa początkowo wyświetla komunikat "ładowanie..." wiersz, a następnie po krótkim opóźnieniu są wyświetlane początkowe dane akcji, a następnie ceny akcji zaczynają się zmieniać.
Skopiuj adres URL z paska adresu przeglądarki i wklej go do co najmniej jednego nowego okna przeglądarki.
Początkowy ekran zapasów jest taki sam jak pierwsza przeglądarka i zmiany odbywają się jednocześnie.
Zamknij wszystkie przeglądarki i otwórz nową przeglądarkę, a następnie przejdź do tego samego adresu URL.
Obiekt singleton StockTicker nadal działa na serwerze, więc w tabeli giełdowej widać, że zapasy nadal się zmieniają. (Nie widzisz początkowej tabeli z zerowymi liczbami zmian).
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:
- Zestawy WebSockets obsługiwane przez usługi IIS 8 i bieżące przeglądarki.
- Zdarzenia wysyłane przez serwer obsługiwane przez przeglądarki inne niż Internet Explorer.
- Na zawsze ramka obsługiwana przez program Internet Explorer.
- Długie sondowanie Ajax obsługiwane przez wszystkie przeglądarki.
W przypadku dowolnego połączenia usługa SignalR wybiera najlepszą metodę transportu, którą obsługuje zarówno serwer, jak i klient.
Otwórz StockTicker.js i dodaj 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);
Naciśnij klawisz F5, aby uruchomić projekt.
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.
W przeglądarce Firefox zainstaluj dodatek Firebug, aby uzyskać okno Konsoli. Jeśli używasz przeglądarki Firefox 19 na Windows 8 (IIS 8), metoda transportu to WebSockets.
Jeśli używasz przeglądarki Firefox 19 w systemie Windows 7 (IIS 7.5), metoda transportu to zdarzenia wysyłane przez serwer.
Instalowanie i przeglądanie pełnego przykładu StockTicker
Aplikacja StockTicker zainstalowana przez pakiet NuGet Microsoft.AspNet.SignalR.Sample 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.
Instalowanie pakietu NuGet SignalR.Sample
W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i kliknij polecenie Zarządzaj pakietami NuGet.
W oknie dialogowym Zarządzanie pakietami NuGet kliknij pozycję Online, wprowadź ciąg SignalR.Sample w polu Wyszukaj w trybie online , a następnie kliknij przycisk Zainstaluj w pakiecie SignalR.Sample .
W pliku Global.asax oznacz ciąg RouteTable.Routes.MapHubs(); wiersz dodany wcześniej w metodzie Application_Start.
Kod w pliku Global.asax nie jest już potrzebny, ponieważ pakiet SignalR.Sample rejestruje trasę signalR w pliku 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(); } } }
Klasa WebActivator, do której odwołuje się atrybut zestawu, znajduje się w pakiecie NuGet WebActivatorEx, który jest instalowany jako zależność pakietu SignalR.Sample.
W Eksplorator rozwiązań rozwiń folder SignalR.Sample, który został utworzony przez zainstalowanie pakietu SignalR.Sample.
W folderze SignalR.Sample kliknij prawym przyciskiem myszy StockTicker.html, a następnie kliknij polecenie 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
Naciśnij klawisz F5, aby uruchomić aplikację.
Oprócz siatki, którą widzieliśmy wcześniej, pełna aplikacja znacznika zapasów pokazuje okno przewijania w poziomie, w którym są wyświetlane te same dane zapasów. Po pierwszym uruchomieniu aplikacji "rynek" jest "zamknięty" i zobaczysz statyczną siatkę i okno znacznika, które nie przewija się.
Po kliknięciu pozycji Otwórz rynek pole Live Stock Ticker zacznie przewijać w poziomie, a serwer zaczyna okresowo emitować zmiany cen akcji na podstawie losowych. Za każdym razem, gdy cena akcji zmienia się, zarówno siatka Live Stock Table , jak i pole Live Stock Ticker są aktualizowane. Gdy zmiana ceny akcji jest dodatnia, akcje są wyświetlane z zielonym tłem, a gdy zmiana jest ujemna, akcje są wyświetlane z czerwonym tłem.
Przycisk Zamknij rynek zatrzymuje zmiany i zatrzymuje przewijanie znacznika, a przycisk Resetuj resetuje wszystkie dane zapasów do stanu początkowego przed rozpoczęciem zmian cen. Jeśli otworzysz więcej okien przeglądarki i przejdziesz do tego samego adresu URL, te same dane będą dynamicznie aktualizowane w każdej przeglądarce. Po kliknięciu jednego z przycisków 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 elemecie div sformatowanym w jednym wierszu według stylów CSS. Znacznik jest inicjowany i aktualizowany w taki sam sposób jak tabela: zastępując symbole zastępcze w <ciągu szablonu li> i dynamicznie dodając <elementy li> do <elementu ul> . Przewijanie jest wykonywane przy użyciu funkcji animowania jQuery, aby zmienić margines lewej listy nieurządkowanej w obiekcie div.
Znacznik akcji HTML:
<h2>Live Stock Ticker</h2>
<div id="stockTicker">
<div class="inner">
<ul>
<li class="loading">loading...</li>
</ul>
</div>
</div>
Arkusz 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;
}
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ć
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();
}
OpenMarket, CloseMarket i Reset są wywoływane w odpowiedzi na przyciski w górnej części strony. Demonstrują wzorzec jednego klienta wyzwalającego zmianę stanu, który jest natychmiast propagowany do wszystkich klientów. Każda z tych metod wywołuje metodę w klasie StockTicker, która wpływa na zmianę stanu rynku, a następnie emituje nowy stan.
W klasie StockTicker stan rynku jest utrzymywany przez właściwość MarketState, która zwraca wartość wyliczenia MarketState:
public MarketState MarketState
{
get { return _marketState; }
private set { _marketState = value; }
}
public enum MarketState
{
Closed,
Open
}
Każda z metod, które zmieniają stan rynku, robi to wewnątrz bloku blokady, ponieważ klasa StockTicker musi być wątkowa:
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 wąteksafe, pole _marketState, które popiera właściwość MarketState, jest oznaczone jako nietrwałe,
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 siatkę, jak i wyświetlacz znacznika, a następnie używa elementu jQuery.Color do migania kolorów czerwonych i zielonych.
Nowe funkcje w SignalR.StockTicker.js włączać i wyłączać przyciski na podstawie stanu rynku, a także zatrzymują lub uruchamiają przewijanie w poziomie okna znacznika. Ponieważ do aplikacji ticker.client jest dodawanych wiele funkcji, funkcja rozszerzenia jQuery służy do dodawania ich.
$.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 klient ma kilka dodatkowych czynności do wykonania: dowiedz się, czy rynek jest otwarty lub zamknięty w celu wywołania odpowiedniej funkcji marketOpened lub marketClosed, i 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ą podłączone do przycisków dopiero po nawiązaniu połączenia, aby kod nie mógł wywołać metod serwera przed ich udostępnieniem.
Następne kroki
W tym samouczku przedstawiono sposób programowania aplikacji SignalR, która emituje komunikaty z serwera do wszystkich połączonych klientów, zarówno okresowo, jak i w odpowiedzi na powiadomienia od dowolnego klienta. Wzorzec korzystania z wielowątkowego pojedynczego wystąpienia do obsługi stanu serwera może być również używany 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 z komunikacją równorzędną, zobacz Wprowadzenie z usługą SignalR i aktualizowaniem w czasie rzeczywistym za pomocą usługi SignalR.
Aby dowiedzieć się więcej zaawansowanych pojęć związanych z programowaniem usługi SignalR, odwiedź następujące witryny dotyczące kodu źródłowego i zasobów usługi SignalR: