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.
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
- Visual Studio 2017 com a carga de trabalho ASP.NET e desenvolvimento para a Web.
Criar o projeto
Esta seção mostra como usar o Visual Studio 2017 para criar um aplicativo Web ASP.NET vazio.
No Visual Studio, crie um aplicativo Web ASP.NET.
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.
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Classe.
Nomeie a classe Stock e adicione-a ao projeto.
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) ePrice
. As outras propriedades dependem de como e quando você definePrice
. Na primeira vez que você definirPrice
, o valor será propagado paraDayOpen
. Depois disso, quando você definePrice
, o aplicativo calcula os valores deChange
propriedade ePercentChange
com base na diferença entrePrice
eDayOpen
.
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
.
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
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Novo Item.
Em Adicionar Novo Item – SignalR.StockTicker, selecione Instalado>Visual C#>Web>SignalR e, em seguida, selecione Classe do Hub do SignalR (v2).
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.
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(); } } }
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
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Classe.
Nomeie a classe StockTicker e adicione-a ao projeto.
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:
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Novo Item.
Em Adicionar Novo Item – SignalR.StockTicker , selecione Instalado>Visual C#>Web e, em seguida, selecione Classe de Inicialização OWIN.
Nomeie a classe Inicialização e selecione OK.
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.
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Página HTML.
Nomeie o arquivo StockTicker e selecione OK.
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 deMapHubs
método.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.
Atualize as referências de script no bloco de código para corresponder às versões dos arquivos de script no projeto.
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.
Em Gerenciador de Soluções, clique com o botão direito do mouse no projeto e selecione Adicionar>Arquivo JavaScript.
Nomeie o arquivo StockTicker e selecione OK.
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.
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.
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.
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.
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.
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:
WebSockets, com suporte do IIS 8 e navegadores atuais.
Eventos enviados pelo servidor, com suporte por navegadores diferentes dos Explorer da Internet.
Quadro para sempre, compatível com Explorer da Internet.
Sondagem longa do Ajax, com suporte de todos os navegadores.
Para qualquer conexão determinada, o SignalR escolhe o melhor método de transporte que o servidor e o cliente dão suporte.
Abra StockTicker.js.
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);
Pressione F5 para executar o projeto.
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
No Gerenciador de Soluções, clique com o botão direito no projeto e escolha Gerenciar Pacotes NuGet.
No Gerenciador de Pacotes NuGet: SignalR.StockTicker, selecione Procurar.
Na origem do pacote, selecione nuget.org.
Insira SignalR.Sample na caixa de pesquisa e selecione Microsoft.AspNet.SignalR.Sample>Install.
Em Gerenciador de Soluções, expanda a pasta SignalR.Sample.
A instalação do pacote SignalR.Sample criou a pasta e seu conteúdo.
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.
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.
Selecione Abrir Mercado.
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.
Selecione Fechar Mercado.
As atualizações da tabela param.
O ticker para de rolar.
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.
Copie a URL do navegador, abra dois outros navegadores e cole as URLs nas barras de endereços.
Você vê os mesmos dados atualizados dinamicamente ao mesmo tempo em cada navegador.
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
, CloseMarket
e 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.