Tutorial: Transmissão de servidor com o SignalR 2

Aviso

Esta documentação não é para a versão mais recente do SignalR. Dê uma olhada em ASP.NET Core SignalR.

Este tutorial mostra como criar um aplicativo Web que usa ASP.NET SignalR 2 para fornecer funcionalidade de transmissão de servidor. A transmissão do servidor significa que o servidor inicia as comunicações enviadas aos clientes.

O aplicativo que você criará neste tutorial simula um ticker de ações, um cenário típico para a funcionalidade de transmissão de servidor. Periodicamente, o servidor atualiza aleatoriamente os preços das ações e transmite as atualizações para todos os clientes conectados. No navegador, os números e símbolos nas colunas Alterar e % mudar dinamicamente em resposta às notificações do servidor. Se você abrir navegadores adicionais para a mesma URL, todos eles mostrarão os mesmos dados e as mesmas alterações nos dados simultaneamente.

Captura de tela mostrando como vários navegadores da Web mostram os mesmos dados atualizados simultaneamente.

Neste tutorial, você:

  • Criar o projeto
  • Configurar o código de servidor
  • Examinar o código do servidor
  • Configurar o código do cliente
  • Examinar o código do cliente
  • Testar o aplicativo
  • Habilitar o registro em log

Importante

Se você não quiser trabalhar nas etapas de criação do aplicativo, instale o pacote SignalR.Sample em um novo projeto de Aplicativo Web vazio ASP.NET. Se você instalar o pacote NuGet sem executar as etapas neste tutorial, deverá seguir as instruções no arquivo readme.txt . Para executar o pacote, você precisa adicionar uma classe de inicialização OWIN que chama o ConfigureSignalR método no pacote instalado. Você receberá um erro se não adicionar a classe de inicialização OWIN. Consulte a seção Instalar o exemplo do StockTicker deste artigo.

Pré-requisitos

Criar o projeto

Esta seção mostra como usar o Visual Studio 2017 para criar um aplicativo Web ASP.NET vazio.

  1. No Visual Studio, crie um aplicativo Web ASP.NET.

    Captura de tela mostrando como criar um aplicativo Web ASP.NET.

  2. Na janela Novo aplicativo Web ASP.NET - SignalR.StockTicker , deixe Vazio selecionado e selecione OK.

Configurar o código de servidor

Nesta seção, você configurará o código executado no servidor.

Criar a classe Stock

Comece criando a classe de modelo stock que você usará para armazenar e transmitir informações sobre um estoque.

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Classe.

  2. Nomeie a classe Stock e adicione-a ao projeto.

  3. Substitua o código no arquivo Stock.cs por este código:

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

    As duas propriedades que você definirá ao criar ações são Symbol (por exemplo, MSFT para Microsoft) e Price. As outras propriedades dependem de como e quando você define Price. Na primeira vez que você definir Price, o valor será propagado para DayOpen. Depois disso, quando você define Price, o aplicativo calcula os valores de Change propriedade e PercentChange com base na diferença entre Price e DayOpen.

Criar as classes StockTickerHub e StockTicker

Você usará a API do Hub SignalR para lidar com a interação de servidor para cliente. Uma StockTickerHub classe derivada da classe SignalR Hub lidará com o recebimento de conexões e chamadas de método de clientes. Você também precisa manter dados de estoque e executar um Timer objeto . O Timer objeto disparará periodicamente atualizações de preço independentes de conexões de cliente. Você não pode colocar essas funções em uma Hub classe, pois os Hubs são transitórios. O aplicativo cria uma Hub instância de classe para cada tarefa no hub, como conexões e chamadas do cliente para o servidor. Portanto, o mecanismo que mantém os dados de estoque, atualiza os preços e transmite as atualizações de preços tem que ser executado em uma classe separada. Você nomeará a classe StockTicker.

Difusão do StockTicker

Você só deseja que uma instância da StockTicker classe seja executada no servidor, portanto, você precisará configurar uma referência de cada StockTickerHub instância para a instância singleton StockTicker . A StockTicker classe precisa ser transmitida aos clientes porque tem os dados de estoque e dispara atualizações, mas StockTicker não é uma Hub classe. A StockTicker classe precisa obter uma referência ao objeto de contexto de conexão do Hub SignalR. Em seguida, ele pode usar o objeto de contexto de conexão SignalR para difundir para clientes.

Criar StockTickerHub.cs

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Novo Item.

  2. Em Adicionar Novo Item – SignalR.StockTicker, selecione Instalado>Visual C#>Web>SignalR e, em seguida, selecione Classe do Hub do SignalR (v2).

  3. Nomeie a classe StockTickerHub e adicione-a ao projeto.

    Esta etapa cria o arquivo de classe StockTickerHub.cs . Simultaneamente, ele adiciona um conjunto de arquivos de script e referências de assembly que dão suporte ao SignalR ao projeto.

  4. Substitua o código no arquivo StockTickerHub.cs por este código:

    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. Salve o arquivo.

O aplicativo usa a classe Hub para definir métodos que os clientes podem chamar no servidor. Você está definindo um método: GetAllStocks(). Quando um cliente se conectar inicialmente ao servidor, ele chamará esse método para obter uma lista de todas as ações com seus preços atuais. O método pode ser executado de forma síncrona e retornar IEnumerable<Stock> porque está retornando dados da memória.

Se o método precisasse obter os dados fazendo algo que envolvesse a espera, como uma pesquisa de banco de dados ou uma chamada de serviço Web, você especificaria Task<IEnumerable<Stock>> como o valor retornado para habilitar o processamento assíncrono. Para obter mais informações, consulte ASP.NET Guia de API dos Hubs do SignalR – Servidor – Quando executar de forma assíncrona.

O HubName atributo especifica como o aplicativo fará referência ao Hub no código JavaScript no cliente. O nome padrão no cliente se você não usar esse atributo é uma versão camelCase do nome da classe, que nesse caso seria stockTickerHub.

Como você verá posteriormente ao criar a StockTicker classe , o aplicativo cria uma instância singleton dessa classe em sua propriedade estática Instance . Essa instância singleton de está na memória, independentemente de StockTicker quantos clientes se conectarem ou desconectarem. Essa instância é o que o GetAllStocks() método usa para retornar informações de estoque atuais.

Criar StockTicker.cs

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Classe.

  2. Nomeie a classe StockTicker e adicione-a ao projeto.

  3. Substitua o código no arquivo StockTicker.cs por este código:

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

Como todos os threads estarão executando a mesma instância do código StockTicker, a classe StockTicker precisa ser thread-safe.

Examinar o código do servidor

Se você examinar o código do servidor, ele ajudará você a entender como o aplicativo funciona.

Armazenando a instância singleton em um campo estático

O código inicializa o campo estático _instance que apoia a Instance propriedade com uma instância da classe . Como o construtor é privado, é a única instância da classe que o aplicativo pode criar. O aplicativo usa a inicialização lenta para o _instance campo. Não é por razões de desempenho. É para garantir que a criação da instância seja thread-safe.

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

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

Sempre que um cliente se conecta ao servidor, uma nova instância da classe StockTickerHub em execução em um thread separado obtém a instância singleton do StockTicker da StockTicker.Instance propriedade estática, como você viu anteriormente na StockTickerHub classe .

Armazenando dados de estoque em um ConcurrentDictionary

O construtor inicializa a _stocks coleção com alguns dados de estoque de exemplo e GetAllStocks retorna os estoques. Como você viu anteriormente, essa coleção de ações é retornada por StockTickerHub.GetAllStocks, que é um método de servidor na classe que os Hub clientes podem chamar.

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

A coleção de ações é definida como um tipo ConcurrentDictionary para segurança de thread. Como alternativa, você pode usar um objeto Dictionary e bloquear explicitamente o dicionário quando fizer alterações nele.

Para este aplicativo de exemplo, não há problema em armazenar dados do aplicativo na memória e perder os dados quando o aplicativo for descartado da StockTicker instância. Em um aplicativo real, você trabalharia com um armazenamento de dados de back-end como um banco de dados.

Atualização periódica dos preços das ações

O construtor inicia um Timer objeto que chama periodicamente métodos que atualizam os preços das ações aleatoriamente.

_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 chama UpdateStockPrices, que passa nulo no parâmetro state. Antes de atualizar os preços, o aplicativo tem um bloqueio no _updateStockPricesLock objeto . O código verifica se outro thread já está atualizando os preços e, em seguida, chama TryUpdateStockPrice cada ação na lista. O TryUpdateStockPrice método decide se deseja alterar o preço das ações e quanto alterá-lo. Se o preço das ações mudar, o aplicativo chamará BroadcastStockPrice para transmitir a alteração do preço das ações para todos os clientes conectados.

O _updatingStockPrices sinalizador designado como volátil para garantir que ele seja thread-safe.

private volatile bool _updatingStockPrices = false;

Em um aplicativo real, o TryUpdateStockPrice método chamaria um serviço Web para pesquisar o preço. Nesse código, o aplicativo usa um gerador de número aleatório para fazer alterações aleatoriamente.

Obtendo o contexto do SignalR para que a classe StockTicker possa transmitir para clientes

Como as alterações de preço se originam aqui no StockTicker objeto , é o objeto que precisa chamar um updateStockPrice método em todos os clientes conectados. Em uma Hub classe, você tem uma API para chamar métodos de cliente, mas StockTicker não deriva da Hub classe e não tem uma referência a nenhum Hub objeto. Para transmitir para clientes conectados, a StockTicker classe precisa obter a instância de contexto do SignalR para a StockTickerHub classe e usá-lo para chamar métodos em clientes.

O código obtém uma referência ao contexto signalr quando cria a instância de classe singleton, passa essa referência para o construtor e o construtor a Clients coloca na propriedade .

Há dois motivos pelos quais você deseja obter o contexto apenas uma vez: obter o contexto é uma tarefa cara e obtê-lo uma vez garante que o aplicativo preserve a ordem pretendida de mensagens enviadas aos clientes.

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

Obter a Clients propriedade do contexto e colocá-la na StockTickerClient propriedade permite que você escreva código para chamar métodos de cliente que têm a mesma aparência que em uma Hub classe. Por exemplo, para transmitir para todos os clientes, você pode escrever Clients.All.updateStockPrice(stock).

O updateStockPrice método que você está chamando BroadcastStockPrice ainda não existe. Você o adicionará mais tarde ao escrever um código executado no cliente. Você pode consultar updateStockPrice aqui porque Clients.All é dinâmico, o que significa que o aplicativo avaliará a expressão em runtime. Quando essa chamada de método for executada, o SignalR enviará o nome do método e o valor do parâmetro para o cliente e, se o cliente tiver um método chamado updateStockPrice, o aplicativo chamará esse método e passará o valor do parâmetro para ele.

Clients.All significa enviar para todos os clientes. O SignalR oferece outras opções para especificar para quais clientes ou grupos de clientes enviar. Para obter mais informações, consulte HubConnectionContext.

Registrar a rota do SignalR

O servidor precisa saber qual URL interceptar e direcionar para o SignalR. Para fazer isso, adicione uma classe de inicialização OWIN:

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Novo Item.

  2. Em Adicionar Novo Item – SignalR.StockTicker , selecione Instalado>Visual C#>Web e, em seguida, selecione Classe de Inicialização OWIN.

  3. Nomeie a classe Inicialização e selecione OK.

  4. Substitua o código padrão no arquivo Startup.cs por este código:

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

Você terminou de configurar o código do servidor. Na próxima seção, você configurará o cliente.

Configurar o código do cliente

Nesta seção, você configurará o código executado no cliente.

Criar a página HTML e o arquivo JavaScript

A página HTML exibirá os dados e o arquivo JavaScript organizará os dados.

Criar StockTicker.html

Primeiro, você adicionará o cliente HTML.

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Página HTML.

  2. Nomeie o arquivo StockTicker e selecione OK.

  3. Substitua o código padrão no arquivo StockTicker.html por este código:

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

    O HTML cria uma tabela com cinco colunas, uma linha de cabeçalho e uma linha de dados com uma única célula que abrange todas as cinco colunas. A linha de dados mostra "carregando..." momentaneamente quando o aplicativo é iniciado. O código JavaScript removerá essa linha e adicionará em suas linhas de lugar com dados de estoque recuperados do servidor.

    As marcas de script especificam:

    • O arquivo de script jQuery.

    • O arquivo de script principal do SignalR.

    • O arquivo de script de proxies do SignalR.

    • Um arquivo de script do StockTicker que você criará mais tarde.

    O aplicativo gera dinamicamente o arquivo de script de proxies do SignalR. Ele especifica a URL "/signalr/hubs" e define métodos de proxy para os métodos na classe Hub, nesse caso, para StockTickerHub.GetAllStocks. Se preferir, você pode gerar esse arquivo JavaScript manualmente usando utilitários signalr. Não se esqueça de desabilitar a criação de arquivo dinâmico na chamada de MapHubs método.

  4. Em Gerenciador de Soluções, expanda Scripts.

    As bibliotecas de script para jQuery e SignalR são visíveis no projeto.

    Importante

    O gerenciador de pacotes instalará uma versão posterior dos scripts do SignalR.

  5. Atualize as referências de script no bloco de código para corresponder às versões dos arquivos de script no projeto.

  6. Em Gerenciador de Soluções, clique com o botão direito do mouse emStockTicker.htmle selecione Definir como Página Inicial.

Criar StockTicker.js

Agora, crie o arquivo JavaScript.

  1. Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Arquivo JavaScript.

  2. Nomeie o arquivo StockTicker e selecione OK.

  3. Adicione este código ao arquivo 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);
    
    });
    

Examinar o código do cliente

Se você examinar o código do cliente, ele ajudará você a saber como o código do cliente interage com o código do servidor para fazer o aplicativo funcionar.

Iniciando a conexão

$.connection refere-se aos proxies do SignalR. O código obtém uma referência ao proxy da classe e o StockTickerHub coloca na ticker variável . O nome do proxy é o nome que foi definido pelo HubName atributo :

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

Depois de definir todas as variáveis e funções, a última linha de código no arquivo inicializa a conexão do SignalR chamando a função SignalR start . A start função é executada de forma assíncrona e retorna um objeto jQuery Deferred. Você pode chamar a função concluída para especificar a função a ser chamada quando o aplicativo concluir a ação assíncrona.

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

Obtendo todas as ações

A init função chama a getAllStocks função no servidor e usa as informações que o servidor retorna para atualizar a tabela de ações. Observe que, por padrão, você precisa usar camelCasing no cliente, mesmo que o nome do método seja maiúsculas e minúsculas no servidor. A regra camelCasing só se aplica a métodos, não a objetos. Por exemplo, você se refere a stock.Symbol e stock.Price, não stock.symbol ou 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 No método , o aplicativo cria HTML para uma linha de tabela para cada objeto de estoque recebido do servidor chamando formatStock para formatar propriedades do stock objeto e, em seguida, chamando supplant para substituir espaços rowTemplate reservados na variável pelos valores de propriedade do stock objeto. O HTML resultante é acrescentado à tabela de ações.

Observação

Você chama init passando-o como uma callback função que é executada após a conclusão da função assíncrona start . Se você chamasse init como uma instrução JavaScript separada depois de chamar start, a função falharia porque ela seria executada imediatamente sem esperar que a função inicial terminasse de estabelecer a conexão. Nesse caso, a init função tentaria chamar a getAllStocks função antes que o aplicativo estabeleça uma conexão de servidor.

Obtendo preços atualizados das ações

Quando o servidor altera o preço de uma ação, ele chama o updateStockPrice em clientes conectados. O aplicativo adiciona a função à propriedade do cliente do stockTicker proxy para disponibilizá-la para chamadas do servidor.

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

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

A updateStockPrice função formata um objeto de estoque recebido do servidor em uma linha de tabela da mesma maneira que na init função . Em vez de acrescentar a linha à tabela, ela localiza a linha atual do estoque na tabela e substitui essa linha pela nova.

Testar o aplicativo

Você pode testar o aplicativo para verificar se ele está funcionando. Você verá todas as janelas do navegador exibirem a tabela de ações dinâmica com os preços das ações flutuando.

  1. Na barra de ferramentas, ative a Depuração de Script e, em seguida, selecione o botão reproduzir para executar o aplicativo no modo de depuração.

    Captura de tela do usuário ativando o modo de depuração e selecionando reproduzir.

    Uma janela do navegador será aberta exibindo a Tabela de Estoque Dinâmico. A tabela de ações mostra inicialmente o "carregamento..." em seguida, depois de um curto período, o aplicativo mostra os dados iniciais das ações e, em seguida, os preços das ações começam a mudar.

  2. Copie a URL do navegador, abra dois outros navegadores e cole as URLs nas barras de endereços.

    A exibição inicial do estoque é a mesma que o primeiro navegador e as alterações ocorrem simultaneamente.

  3. Feche todos os navegadores, abra um novo navegador e vá para a mesma URL.

    O objeto singleton StockTicker continuou a ser executado no servidor. A Tabela dinâmica de ações mostra que as ações continuaram a mudar. Você não vê a tabela inicial sem números de alteração.

  4. Feche o navegador.

Habilitar o registro em log

O SignalR tem uma função de log interna que você pode habilitar no cliente para ajudar na solução de problemas. Nesta seção, você habilita o registro em log e vê exemplos que mostram como os logs informam quais dos seguintes métodos de transporte o SignalR está usando:

Para qualquer conexão determinada, o SignalR escolhe o melhor método de transporte que o servidor e o cliente dão suporte.

  1. Abra StockTicker.js.

  2. Adicione essa linha de código realçada para habilitar o registro em log imediatamente antes do código que inicializa a conexão no final do arquivo:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  3. Pressione F5 para executar o projeto.

  4. Abra a janela ferramentas de desenvolvedor do navegador e selecione o Console para ver os logs. Talvez seja necessário atualizar a página para ver os logs do SignalR negociando o método de transporte para uma nova conexão.

    • Se você estiver executando o Internet Explorer 10 no Windows 8 (IIS 8), o método de transporte será WebSockets.

    • Se você estiver executando o Internet Explorer 10 no Windows 7 (IIS 7.5), o método de transporte será iframe.

    • Se você estiver executando o Firefox 19 no Windows 8 (IIS 8), o método de transporte será WebSockets.

      Dica

      No Firefox, instale o suplemento Firebug para obter uma janela console.

    • Se você estiver executando o Firefox 19 no Windows 7 (IIS 7.5), o método de transporte será eventos enviados pelo servidor .

Instalar o exemplo do StockTicker

O Microsoft.AspNet.SignalR.Sample instala o aplicativo StockTicker. O pacote NuGet inclui mais recursos do que a versão simplificada que você criou do zero. Nesta seção do tutorial, você instalará o pacote NuGet e examinará os novos recursos e o código que os implementa.

Importante

Se você instalar o pacote sem executar as etapas anteriores deste tutorial, deverá adicionar uma classe de inicialização OWIN ao seu projeto. Esse readme.txt arquivo para o pacote NuGet explica essa etapa.

Instalar o pacote NuGet SignalR.Sample

  1. No Gerenciador de Soluções, clique com o botão direito no projeto e escolha Gerenciar Pacotes NuGet.

  2. No Gerenciador de Pacotes NuGet: SignalR.StockTicker, selecione Procurar.

  3. Na origem do pacote, selecione nuget.org.

  4. Insira SignalR.Sample na caixa de pesquisa e selecione Microsoft.AspNet.SignalR.Sample>Install.

  5. Em Gerenciador de Soluções, expanda a pasta SignalR.Sample.

    A instalação do pacote SignalR.Sample criou a pasta e seu conteúdo.

  6. Na pasta SignalR.Sample , clique com o botão direito do mouse emStockTicker.htmle selecione Definir como Página Inicial.

    Observação

    A instalação do pacote NuGet SignalR.Sample pode alterar a versão do jQuery que você tem na pasta Scripts . O novo arquivoStockTicker.html que o pacote instala na pasta SignalR.Sample estará sincronizado com a versão jQuery que o pacote instala, mas se você quiser executar seu arquivo deStockTicker.html original novamente, talvez seja necessário atualizar a referência jQuery na marca de script primeiro.

Executar o aplicativo

A tabela que você viu no primeiro aplicativo tinha recursos úteis. O aplicativo de ticker de ações completo mostra novos recursos: uma janela de rolagem horizontal que mostra os dados de estoque e os estoques que mudam de cor à medida que sobem e caem.

  1. Pressione F5 para executar o aplicativo.

    Quando você executa o aplicativo pela primeira vez, o "mercado" é "fechado" e você vê uma tabela estática e uma janela de ticker que não está rolando.

  2. Selecione Abrir Mercado.

    Captura de tela do ticker dinâmico.

    • A caixa Escala de Ações Dinâmicas começa a rolar horizontalmente e o servidor começa a difundir periodicamente as alterações de preço das ações aleatoriamente.

    • Sempre que um preço das ações muda, o aplicativo atualiza a Tabela de Ações Dinâmicas e o Live Stock Ticker.

    • Quando a alteração de preço de uma ação é positiva, o aplicativo mostra as ações com um plano de fundo verde.

    • Quando a alteração é negativa, o aplicativo mostra o estoque com um plano de fundo vermelho.

  3. Selecione Fechar Mercado.

    • As atualizações da tabela param.

    • O ticker para de rolar.

  4. Selecione Restaurar.

    • Todos os dados de estoque são redefinidos.

    • O aplicativo restaura o estado inicial antes que as alterações de preço sejam iniciadas.

  5. Copie a URL do navegador, abra dois outros navegadores e cole as URLs nas barras de endereços.

  6. Você vê os mesmos dados atualizados dinamicamente ao mesmo tempo em cada navegador.

  7. Quando você seleciona qualquer um dos controles, todos os navegadores respondem da mesma maneira ao mesmo tempo.

Live Stock Ticker display

A exibição do Live Stock Ticker é uma lista não ordenada em um <div> elemento formatado em uma única linha por estilos CSS. O aplicativo inicializa e atualiza o ticker da mesma maneira que a tabela: substituindo espaços reservados em uma <li> cadeia de caracteres de modelo e adicionando dinamicamente os <li> elementos ao <ul> elemento . O aplicativo inclui a rolagem usando a função jQuery animate para variar a margem esquerda da lista não ordenada dentro do <div>.

StockTicker.html do SignalR.Sample

O código HTML do ticker de ações:

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

SignalR.Sample StockTicker.css

O código CSS do ticker de ações:

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

O código jQuery que o faz rolar:

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

Métodos adicionais no servidor que o cliente pode chamar

Para adicionar flexibilidade ao aplicativo, há métodos adicionais que o aplicativo pode chamar.

SignalR.Sample StockTickerHub.cs

A StockTickerHub classe define quatro métodos adicionais que o cliente pode chamar:

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

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

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

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

O aplicativo chama OpenMarket, CloseMarkete Reset em resposta aos botões na parte superior da página. Eles demonstram o padrão de um cliente disparando uma alteração no estado propagada imediatamente para todos os clientes. Cada um desses métodos chama um método na classe que causa a alteração do estado de mercado e, em StockTicker seguida, transmite o novo estado.

SignalR.Sample StockTicker.cs

StockTicker Na classe , o aplicativo mantém o estado do mercado com uma MarketState propriedade que retorna um MarketState valor de enumeração:

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

public enum MarketState
{
    Closed,
    Open
}

Cada um dos métodos que alteram o estado de mercado faz isso dentro de um bloco de bloqueio porque a StockTicker classe precisa ser thread-safe:

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

Para garantir que esse código seja thread-safe, o _marketState campo que apoia a MarketState propriedade designada volatile:

private volatile MarketState _marketState;

Os BroadcastMarketStateChange métodos e BroadcastMarketReset são semelhantes ao método BroadcastStockPrice que você já viu, exceto que eles chamam diferentes métodos definidos no cliente:

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

Funções adicionais no cliente que o servidor pode chamar

A updateStockPrice função agora manipula a tabela e a exibição do ticker, e usa para piscar cores vermelhas e verdes jQuery.Color .

Novas funções no SignalR.StockTicker.js habilitar e desabilitar os botões com base no estado de mercado. Eles também param ou iniciam a rolagem horizontal do Live Stock Ticker . Como muitas funções estão sendo adicionadas ao ticker.client, o aplicativo usa a função de extensão jQuery para adicioná-las.

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

Configuração adicional do cliente após estabelecer a conexão

Depois que o cliente estabelece a conexão, ele tem algum trabalho adicional a ser feito:

  • Descubra se o mercado está aberto ou fechado para chamar a função ou marketClosed apropriadamarketOpened.

  • Anexe as chamadas de método de servidor aos botões.

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

Os métodos do servidor não são conectados aos botões até que o aplicativo estabeleça a conexão. É para que o código não possa chamar os métodos do servidor antes que eles estejam disponíveis.

Recursos adicionais

Neste tutorial, você aprendeu a programar um aplicativo SignalR que transmite mensagens do servidor para todos os clientes conectados. Agora você pode transmitir mensagens periodicamente e em resposta a notificações de qualquer cliente. Você pode usar o conceito de instância singleton de vários threads para manter o estado do servidor em cenários de jogos online de vários jogadores. Para obter um exemplo, consulte o jogo ShootR baseado no SignalR.

Para obter tutoriais que mostram cenários de comunicação ponto a ponto, consulte Introdução com o SignalR e Atualização em tempo real com o SignalR.

Para obter mais informações sobre o SignalR, consulte os seguintes recursos:

Próximas etapas

Neste tutorial, você:

  • Criou o projeto
  • Configurar o código de servidor
  • Examinou o código do servidor
  • Configurar o código do cliente
  • Examinou o código do cliente
  • Testado o aplicativo
  • Log habilitado

Avance para o próximo artigo para saber como criar um aplicativo Web em tempo real que usa ASP.NET SignalR 2.