Руководство. Широковещательная трансляция сервера с помощью SignalR 2

Предупреждение

Эта документация не подходит для последней версии SignalR. Ознакомьтесь с ASP.NET Core SignalR.

В этом руководстве показано, как создать веб-приложение, которое использует ASP.NET SignalR 2 для предоставления функций широковещательного вещания сервера. Широковещательная трансляция сервера означает, что сервер запускает обмен данными, отправляемые клиентам.

Приложение, которое вы создадите в этом руководстве, имитирует тиккер акций, типичный сценарий для широковещательных функций сервера. Периодически сервер случайным образом обновляет цены на акции и передает обновления всем подключенным клиентам. В браузере числа и символы в столбцах Изменения и % динамически изменяются в ответ на уведомления с сервера. Если вы открываете дополнительные браузеры с тем же URL-адресом, они отображают одни и те же данные и те же изменения одновременно.

Снимок экрана, показывающий, как в нескольких веб-браузерах одновременно отображаются одни и те же обновленные данные.

Изучив это руководство, вы:

  • Создание проекта
  • Настройка кода сервера
  • Изучение кода сервера
  • Настройка клиентского кода
  • Изучение клиентского кода
  • Тестирование приложения
  • Включение ведения журналов

Важно!

Если вы не хотите выполнять шаги по созданию приложения, можно установить пакет SignalR.Sample в новом проекте пустого веб-приложения ASP.NET. Если вы устанавливаете пакет NuGet без выполнения действий, описанных в этом руководстве, необходимо выполнить инструкции в файлеreadme.txt . Чтобы запустить пакет, необходимо добавить класс запуска OWIN, который вызывает ConfigureSignalR метод в установленном пакете. Если не добавить класс запуска OWIN, появится сообщение об ошибке. См. раздел Установка примера StockTicker этой статьи.

Предварительные требования

  • Visual Studio 2017 с рабочей нагрузкой ASP.NET и веб-разработка.

Создание проекта

В этом разделе показано, как использовать Visual Studio 2017 для создания пустого веб-приложения ASP.NET.

  1. В Visual Studio создайте веб-приложение ASP.NET.

    Снимок экрана: создание веб-приложения ASP.NET.

  2. В окне Новое веб-приложение ASP.NET — SignalR.StockTicker оставьте флажок Пустой и нажмите кнопку ОК.

Настройка кода сервера

В этом разделе вы настроите код, который выполняется на сервере.

Создание класса Stock

Для начала вы создадите класс модели Stock , который будет использоваться для хранения и передачи сведений о акции.

  1. В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>класс.

  2. Назовите класс Stock и добавьте его в проект.

  3. Замените код в файле Stock.cs следующим кодом:

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

    Два свойства, которые вы задали при создании акций Symbol : (например, MSFT для Майкрософт) и Price. Другие свойства зависят от того, как и когда вы задаете Price. При первом установке Priceзначение распространяется на DayOpen. После этого при установке Priceприложение вычисляет значения свойств и PercentChange на основе разницы Change между Price и DayOpen.

Создание классов StockTickerHub и StockTicker

Вы будете использовать API-интерфейс Концентратора SignalR для обработки взаимодействия между сервером и клиентом. Класс StockTickerHub , производный от класса SignalR, Hub будет обрабатывать получение подключений и вызовов методов от клиентов. Кроме того, необходимо хранить данные о запасах и запускать Timer объект . Объект Timer будет периодически запускать обновления цен независимо от клиентских подключений. Эти функции нельзя поместить в Hub класс, так как центры являются временными. Приложение создает экземпляр класса для каждой Hub задачи в концентраторе, например для подключений и вызовов клиента к серверу. Таким образом, механизм, который хранит данные о акциях, обновляет цены и транслирует обновления цен, должен работать в отдельном классе. Вы присвойте классу StockTickerимя .

Вещание из StockTicker

На сервере StockTicker должен выполняться только один экземпляр класса , поэтому необходимо настроить ссылку из каждого StockTickerHub экземпляра на отдельный StockTicker экземпляр. Класс StockTicker должен быть широковещательным для клиентов, так как он содержит данные о запасах и активирует обновления, но StockTicker не является классом Hub . Класс StockTicker должен получить ссылку на объект контекста подключения Концентратор SignalR. Затем он может использовать объект контекста подключения SignalR для трансляции клиентам.

Создание Файла StockTickerHub.cs

  1. В Обозреватель решений щелкните проект правой кнопкой мыши и выберите добавить>новый элемент.

  2. В разделе Добавление нового элемента — SignalR.StockTicker выберите Установленные>Visual C#>Web>SignalR, а затем — Класс концентратора SignalR (версия 2).

  3. Назовите класс StockTickerHub и добавьте его в проект.

    На этом шаге создается файл класса StockTickerHub.cs . Одновременно он добавляет в проект набор файлов скриптов и ссылок на сборки, которые поддерживают SignalR.

  4. Замените код в файле StockTickerHub.cs следующим кодом:

    using System.Collections.Generic;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        [HubName("stockTickerMini")]
        public class StockTickerHub : Hub
        {
            private readonly StockTicker _stockTicker;
    
            public StockTickerHub() : this(StockTicker.Instance) { }
    
            public StockTickerHub(StockTicker stockTicker)
            {
                _stockTicker = stockTicker;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stockTicker.GetAllStocks();
            }
        }
    }
    
  5. Сохраните файл.

Приложение использует класс Hub для определения методов, которые клиенты могут вызывать на сервере. Вы определяете один метод: GetAllStocks(). Когда клиент изначально подключается к серверу, он вызывает этот метод, чтобы получить список всех акций с текущими ценами. Метод может выполняться синхронно и возвращать IEnumerable<Stock> , так как он возвращает данные из памяти.

Если метод должен был получить данные, выполнив какие-либо действия, требующие ожидания, например подстановку базы данных или вызов веб-службы, необходимо указать Task<IEnumerable<Stock>> в качестве возвращаемого значения, чтобы включить асинхронную обработку. Дополнительные сведения см . в ASP.NET Руководстве по API Центров SignalR — сервер — когда выполнять асинхронно.

Атрибут HubName указывает, как приложение будет ссылаться на концентратор в коде JavaScript на клиенте. Имя по умолчанию на клиенте, если вы не используете этот атрибут, — это версия camelCase имени класса, которая в данном случае будет иметь значение stockTickerHub.

Как вы увидите позже при создании StockTicker класса, приложение создает одноэлементный экземпляр этого класса в своем статическом Instance свойстве. Этот одноэлементный StockTicker экземпляр находится в памяти независимо от того, сколько клиентов подключается или отключается. Этот экземпляр используется методом GetAllStocks() для возврата текущих сведений о запасах.

Создание Файла StockTicker.cs

  1. В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>класс.

  2. Назовите класс StockTicker и добавьте его в проект.

  3. Замените код в файле StockTicker.cs следующим кодом:

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

Так как все потоки будут выполнять один и тот же экземпляр кода StockTicker, класс StockTicker должен быть потокобезопасным.

Изучение кода сервера

Если вы изучите код сервера, это поможет вам понять, как работает приложение.

Хранение одноэлементного экземпляра в статическом поле

Код инициализирует статическое _instance поле, которое поддерживает Instance свойство экземпляром класса . Поскольку конструктор является закрытым, это единственный экземпляр класса, который может создать приложение. Приложение использует отложенную инициализацию_instance для поля. Это не из соображений производительности. Это необходимо, чтобы убедиться, что создание экземпляра является потокобезопасно.

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

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

Каждый раз, когда клиент подключается к серверу, новый экземпляр класса StockTickerHub, выполняющийся в отдельном потоке, получает одноэлементный экземпляр StockTicker из StockTicker.Instance статического свойства, как вы видели ранее в StockTickerHub классе .

Хранение данных о запасах в ConcurrentDictionary

Конструктор инициализирует коллекцию _stocks с помощью некоторых выборочных данных о запасах и GetAllStocks возвращает запасы. Как вы видели ранее, эта коллекция запасов возвращается методом StockTickerHub.GetAllStocks, который является серверным методом в Hub классе, который клиенты могут вызывать.

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

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

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

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

Коллекция запасов определяется как тип ConcurrentDictionary для потокобезопасности. В качестве альтернативы можно использовать объект Dictionary и явно заблокировать словарь при внесении изменений в него.

Для этого примера приложения можно хранить данные приложения в памяти и терять их, когда приложение удаляет StockTicker экземпляр. В реальном приложении вы будете работать с серверным хранилищем данных, например с базой данных.

Периодическое обновление цен на акции

Конструктор запускает Timer объект , который периодически вызывает методы, которые обновляют цены на акции случайным образом.

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

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

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

            _updatingStockPrices = false;
        }
    }
}

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

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

    stock.Price += change;
    return true;
}

Timer вызывает UpdateStockPrices, который передает значение NULL в параметре state. Перед обновлением цен приложение блокирует _updateStockPricesLock объект . Код проверяет, обновляет ли другой поток цены, а затем вызывает TryUpdateStockPrice для каждой акции в списке. Метод TryUpdateStockPrice решает, следует ли изменять цену акций и сколько ее изменить. Если цена акций меняется, приложение вызывает трансляцию BroadcastStockPrice изменения цен акций всем подключенным клиентам.

Флаг _updatingStockPrices , назначенный переменным , чтобы убедиться, что он является потокобезопасным.

private volatile bool _updatingStockPrices = false;

В реальном приложении метод вызывает веб-службу для TryUpdateStockPrice поиска цены. В этом коде приложение использует генератор случайных чисел для случайных изменений.

Получение контекста SignalR для трансляции класса StockTicker клиентам

Так как изменения цен происходят здесь в объекте StockTicker , именно объект должен вызывать updateStockPrice метод для всех подключенных клиентов. Hub В классе есть API для вызова клиентских методов, но StockTicker он не является производным Hub от класса и не имеет ссылки на какой-либо Hub объект. Для широковещательной передачи подключенным StockTicker клиентам класс должен получить экземпляр контекста SignalR для класса и использовать его для StockTickerHub вызова методов на клиентах.

Код получает ссылку на контекст SignalR при создании экземпляра одноэлементного класса, передает ее конструктору, а конструктор помещает ее в Clients свойство .

Существует две причины, по которым требуется получить контекст только один раз: получение контекста является дорогостоящей задачей, а однократное получение гарантирует, что приложение сохраняет нужный порядок сообщений, отправляемых клиентам.

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

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

    // Remainder of constructor ...
}

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

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

Clients Получение свойства контекста и его помещение в StockTickerClient свойство позволяет написать код для вызова клиентских методов, который выглядит так же, как в Hub классе. Например, для трансляции на всех клиентах можно написать Clients.All.updateStockPrice(stock).

Метод updateStockPrice , который вы вызываете, BroadcastStockPrice еще не существует. Вы добавите его позже при написании кода, который выполняется на клиенте. Здесь можно ссылаться, updateStockPrice так как Clients.All является динамическим. Это означает, что приложение будет оценивать выражение во время выполнения. При выполнении этого вызова метода SignalR отправит клиенту имя метода и значение параметра, а если у клиента есть метод с именем updateStockPrice, приложение вызовет этот метод и передаст ему значение параметра.

Clients.All означает отправку всем клиентам. SignalR предоставляет другие параметры для указания клиентов или групп клиентов для отправки. Дополнительные сведения см. в разделе HubConnectionContext.

Регистрация маршрута SignalR

Сервер должен знать, какой URL-адрес следует перехватить и направить в SignalR. Для этого добавьте класс запуска OWIN:

  1. В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>новый элемент.

  2. В разделе Добавление нового элемента — SignalR.StockTicker выберите Установленные>Visual C#>Web , а затем — Класс запуска OWIN.

  3. Назовите класс Startup и нажмите кнопку ОК.

  4. Замените код по умолчанию в файле Startup.cs следующим кодом:

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

Теперь вы завершили настройку кода сервера. В следующем разделе вы настроите клиент.

Настройка кода клиента

В этом разделе вы настроите код, который выполняется на клиенте.

Создание HTML-страницы и файла JavaScript

На HTML-странице отобразятся данные, а файл JavaScript упорядочит данные.

Создание StockTicker.html

Сначала добавьте HTML-клиент.

  1. В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>HTML-страницу.

  2. Назовите файл StockTicker и нажмите кнопку ОК.

  3. Замените код по умолчанию в файле StockTicker.html следующим кодом:

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

    HTML-код создает таблицу с пятью столбцами, строку заголовка и строку данных с одной ячейкой, охватывающей все пять столбцов. В строке данных отображается сообщение "загрузка..." на мгновение при запуске приложения. Код JavaScript удалит эту строку и добавит на ее место строки со стандартными данными, полученными с сервера.

    Теги скрипта указывают:

    • Файл скрипта jQuery.

    • Файл основного скрипта SignalR.

    • Файл скрипта прокси-серверов SignalR.

    • Файл скрипта StockTicker, который вы создадите позже.

    Приложение динамически создает файл скрипта прокси-серверов SignalR. Он задает URL-адрес "/signalr/hubs" и определяет методы прокси-сервера для методов в классе концентратора, в данном случае для StockTickerHub.GetAllStocks. При желании этот файл JavaScript можно создать вручную с помощью служебных программ SignalR. Не забудьте отключить динамическое создание файла в вызове MapHubs метода.

  4. В Обозреватель решений разверните узел Скрипты.

    Библиотеки скриптов для jQuery и SignalR видны в проекте.

    Важно!

    Диспетчер пакетов установит более позднюю версию скриптов SignalR.

  5. Обновите ссылки на скрипты в блоке кода в соответствии с версиями файлов скриптов в проекте.

  6. В Обозреватель решений щелкните правой кнопкой мыши StockTicker.htmlи выберите пункт Задать как начальную страницу.

Создание StockTicker.js

Теперь создайте файл JavaScript.

  1. В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>файл JavaScript.

  2. Назовите файл StockTicker и нажмите кнопку ОК.

  3. Добавьте следующий код в файлStockTicker.js :

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

Изучение клиентского кода

Изучение клиентского кода поможет узнать, как клиентский код взаимодействует с серверным кодом, чтобы обеспечить работу приложения.

Запуск подключения

$.connection ссылается на прокси-серверы SignalR. Код получает ссылку на прокси-сервер для StockTickerHub класса и помещает его в переменную ticker . Имя прокси-сервера — это имя, заданное атрибутом HubName :

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

После определения всех переменных и функций последняя строка кода в файле инициализирует подключение SignalR путем вызова функции SignalR start . Функция start выполняется асинхронно и возвращает объект jQuery Deferred. Можно вызвать функцию done, чтобы указать функцию, вызываемую по завершении асинхронного действия приложением.

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

Получение всех запасов

Функция init вызывает функцию getAllStocks на сервере и использует сведения, возвращаемые сервером, для обновления биржевой таблицы. Обратите внимание, что по умолчанию необходимо использовать camelCasing на клиенте, даже если на сервере используется имя метода pascal. Правило camelCasing применяется только к методам, а не к объектам. Например, вы ссылаетесь на stock.Symbol и stock.Price, а не stock.symbol на или stock.price.

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

В методе init приложение создает HTML-код для строки таблицы для каждого стандартного объекта, полученного stock с сервера, путем вызова formatStock для форматирования свойств объекта, а затем путем замены supplant заполнителей в rowTemplate переменной значениями stock свойств объекта. Полученный HTML-код добавляется в таблицу stock.

Примечание

Вызов выполняется init путем передачи его в качестве callback функции, которая выполняется после завершения асинхронной start функции. Если вы вызвали init как отдельный оператор JavaScript после вызова start, функция завершится ошибкой, так как она будет выполняться немедленно, не дожидаясь завершения подключения функции start. В этом случае функция попытается вызвать функцию getAllStocks до того, init как приложение установит подключение к серверу.

Получение обновленных цен на акции

Когда сервер изменяет цену акций, он вызывает на подключенных updateStockPrice клиентах. Приложение добавляет функцию в свойство stockTicker клиента прокси-сервера, чтобы сделать ее доступной для вызовов с сервера.

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

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

Функция updateStockPrice форматирует стандартный объект, полученный от сервера, в строку таблицы так же, как и init в функции. Вместо добавления строки в таблицу она находит текущую строку акций в таблице и заменяет ее новой.

Тестирование приложения

Вы можете протестировать приложение, чтобы убедиться, что оно работает. Вы увидите, что во всех окнах браузера отображается таблица акций с колебаниями цен на акции.

  1. На панели инструментов включите отладку скриптов и нажмите кнопку воспроизведения, чтобы запустить приложение в режиме отладки.

    Снимок экрана: пользователь включает режим отладки и выбирает воспроизведение.

    Откроется окно браузера с таблицей Live Stock. В таблице акций изначально отображается "загрузка..." затем через некоторое время приложение отображает исходные данные акций, а затем цены на акции начинают меняться.

  2. Скопируйте URL-адрес из браузера, откройте два других браузера и вставьте URL-адреса в адресные строки.

    Начальное отображение акций совпадает с первым браузером, и изменения происходят одновременно.

  3. Закройте все браузеры, откройте новый браузер и перейдите по тому же URL-адресу.

    Одноэлементный объект StockTicker продолжал выполняться на сервере. Таблица live stock показывает, что запасы продолжают меняться. Начальная таблица с нулевыми цифрами изменений не отображается.

  4. Закройте браузер.

Включение ведения журналов

SignalR имеет встроенную функцию ведения журнала, которую можно включить на клиенте для устранения неполадок. В этом разделе описано, как включить ведение журнала и просмотреть примеры, показывающие, как журналы сообщают вам, какой из следующих методов транспорта использует SignalR:

Для любого подключения SignalR выбирает оптимальный метод транспорта, который поддерживается как сервером, так и клиентом.

  1. Откройте StockTicker.js.

  2. Добавьте выделенную строку кода, чтобы включить ведение журнала непосредственно перед кодом, который инициализирует подключение в конце файла:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  3. Нажмите клавишу F5 , чтобы запустить проект.

  4. Откройте окно средств разработчика в браузере и выберите Консоль, чтобы просмотреть журналы. Возможно, потребуется обновить страницу, чтобы просмотреть журналы SignalR, которые согласовывают метод транспорта для нового подключения.

    • Если вы используете Internet Обозреватель 10 на Windows 8 (IIS 8), методом транспорта будет WebSockets.

    • Если вы используете Интернет Обозреватель 10 в Windows 7 (IIS 7.5), методом транспорта является iframe.

    • Если вы используете Firefox 19 на Windows 8 (IIS 8), методом транспорта будет WebSocket.

      Совет

      В Firefox установите надстройку Firebug, чтобы получить окно консоли.

    • Если вы используете Firefox 19 в Windows 7 (IIS 7.5), методом транспорта являются события , отправляемые сервером .

Установка примера StockTicker

Microsoft.AspNet.SignalR.Sample устанавливает приложение StockTicker. Пакет NuGet содержит больше возможностей, чем упрощенная версия, созданная с нуля. В этом разделе учебника вы установите пакет NuGet и изучите новые функции и код, который их реализует.

Важно!

Если вы устанавливаете пакет без выполнения предыдущих шагов, описанных в этом руководстве, необходимо добавить класс запуска OWIN в проект. Этот шаг описан в файле readme.txt для пакета NuGet.

Установка пакета NuGet SignalR.Sample

  1. В обозревателе решений щелкните проект правой кнопкой мыши и выберите Управление пакетами NuGet.

  2. В диспетчере пакетов NuGet: SignalR.StockTicker нажмите кнопку Обзор.

  3. В поле Источник пакета выберите nuget.org.

  4. Введите SignalR.Sample в поле поиска и выберите Microsoft.AspNet.SignalR.Sample>Install.

  5. В Обозреватель решений разверните папку SignalR.Sample.

    При установке пакета SignalR.Sample была создана папка и ее содержимое.

  6. В папке SignalR.Sample щелкните правой кнопкой мыши StockTicker.html, а затем выберите пункт Задать как начальную страницу.

    Примечание

    Установка пакета NuGet SignalR.Sample может изменить версию jQuery в папке Scripts . Новый файлStockTicker.html , который пакет устанавливает в папке SignalR.Sample , будет синхронизирован с версией jQuery, устанавливаемой пакетом, но если вы хотите снова запустить исходный StockTicker.html файл, может потребоваться сначала обновить ссылку jQuery в теге скрипта.

Выполнение приложения

Таблица, которую вы видели в первом приложении, имела полезные функции. В приложении полного тикера акций отображаются новые функции: горизонтальное прокручиваемое окно с данными о акциях и акциями, которые изменяют цвет по мере их роста и падения.

  1. Нажмите клавишу F5 , чтобы запустить приложение.

    При первом запуске приложения "рынок" закрывается, и вы увидите статическую таблицу и окно с тикером, которое не прокручивается.

  2. Выберите Открыть рынок.

    Снимок экрана: динамический тикер.

    • Поле Live Stock Ticker начинает прокручиваться по горизонтали, а сервер начинает периодически транслировать изменения цен на акции на случайной основе.

    • Каждый раз, когда цена акций меняется, приложение обновляет как таблицу live stock , так и live stock ticker.

    • Если цена на акции изменяется положительно, приложение отображает акции с зеленым фоном.

    • Если изменение отрицательное, приложение отображает акции на красном фоне.

  3. Выберите Закрыть рынок.

    • Обновление таблицы останавливается.

    • Тикер останавливает прокрутку.

  4. Выберите Сброс.

    • Все данные акций сбрасываются.

    • Приложение восстанавливает исходное состояние до начала изменения цен.

  5. Скопируйте URL-адрес из браузера, откройте два других браузера и вставьте URL-адреса в адресные строки.

  6. Одни и те же данные динамически обновляются одновременно в каждом браузере.

  7. При выборе любого из элементов управления все браузеры отвечают одинаково.

Дисплей Live Stock Ticker

Отображение Live Stock Ticker — это неупорядоченный список в элементе <div> , отформатированный в одну строку стилей CSS. Приложение инициализирует и обновляет тикер так же, как и таблица: заменяя заполнители в строке <li> шаблона и динамически добавляя <li> элементы в <ul> элемент . Приложение включает прокрутку с помощью функции jQuery animate для изменения левого края неупорядоченного списка в <div>.

StockTicker.html SignalR.Sample

HTML-код stock ticker:

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

SignalR.Sample StockTicker.css

Код CSS для тикера акций:

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

    #stockTicker .inner {
        width: 9999px;
    }

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

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

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

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

SignalR.StockTicker.js SignalR.Sample

Код jQuery, который выполняет прокрутку:

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

Дополнительные методы на сервере, которые клиент может вызывать

Для повышения гибкости приложения существуют дополнительные методы, которые приложение может вызывать.

SignalR.Sample StockTickerHub.cs

Класс StockTickerHub определяет четыре дополнительных метода, которые клиент может вызывать:

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

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

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

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

Приложение вызывает OpenMarket, CloseMarketи Reset в ответ на кнопки в верхней части страницы. Они демонстрируют шаблон одного клиента, инициирующего изменение состояния, немедленно распространяемого на всех клиентов. Каждый из этих методов вызывает метод в StockTicker классе , который вызывает изменение состояния рынка, а затем транслирует новое состояние.

SignalR.Sample StockTicker.cs

StockTicker В классе приложение поддерживает состояние рынка с MarketState помощью свойства, возвращающего значение перечисленияMarketState:

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

public enum MarketState
{
    Closed,
    Open
}

Каждый из методов, изменяющих состояние рынка, делает это внутри блока блокировки, так как StockTicker класс должен быть потокобезопасный:

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

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

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

Чтобы убедиться, что этот код является потокобезопасным, поле, _marketState которое поддерживает MarketState свойство, назначенное volatile:

private volatile MarketState _marketState;

Методы BroadcastMarketStateChange и BroadcastMarketReset похожи на метод BroadcastStockPrice, который вы уже видели, за исключением того, что они вызывают различные методы, определенные в клиенте:

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

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

Дополнительные функции на клиенте, которые сервер может вызывать

Теперь updateStockPrice функция обрабатывает как таблицу, так и отображение тикера и использует jQuery.Color для вспышки красные и зеленые цвета.

Новые функции в SignalR.StockTicker.js включать и отключать кнопки в зависимости от состояния рынка. Они также останавливают или запускают горизонтальную прокрутку Live Stock Ticker . Так как многие функции добавляются в ticker.client, приложение использует функцию расширения jQuery для их добавления.

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

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

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

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

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

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

Дополнительная настройка клиента после установки подключения

После того как клиент установит подключение, ему нужно выполнить некоторые дополнительные действия:

  • Узнайте, открыт ли рынок или закрыт для вызова соответствующей marketOpened функции или marketClosed .

  • Подключите вызовы метода сервера к кнопкам.

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

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

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

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

Серверные методы не подключены к кнопкам до тех пор, пока приложение не установит подключение. Это значит, что код не может вызывать методы сервера до их появления.

Дополнительные ресурсы

В этом руководстве вы узнали, как запрограммировать приложение SignalR, которое передает сообщения с сервера всем подключенным клиентам. Теперь вы можете периодически транслировать сообщения в ответ на уведомления от любого клиента. Вы можете использовать концепцию однопоточного одноэлементного экземпляра для поддержания состояния сервера в сценариях многопользовательских онлайн-игр. Пример см. в разделе Игра ShootR на основе SignalR.

Руководства по сценариям однорангового взаимодействия см. в разделах начало работы с SignalR и Обновление в режиме реального времени с помощью SignalR.

Дополнительные сведения о SignalR см. в следующих ресурсах:

Дальнейшие действия

Изучив это руководство, вы:

  • Создание проекта
  • Настройка кода сервера
  • Просмотр кода сервера
  • Настройка клиентского кода
  • Просмотр кода клиента
  • тестирование приложения.
  • Включено ведение журнала

Перейдите к следующей статье, чтобы узнать, как создать веб-приложение в режиме реального времени, использующее ASP.NET SignalR 2.