Como implementar dados em tempo real para uma página da web

(Esta é uma pergunta estilo Q / A, destinada a ser um recurso para pessoas que fazem perguntas semelhantes. Muitas pessoas parecem tropeçar na melhor maneira de fazer isso porque não conhecem todas as opções. Muitas das respostas serão específicas do ASP.NET, mas o AJAX e outras técnicas têm equivalentes em outros frameworks, como o socket.io e o SignalR.

Eu tenho uma tabela de dados que eu implementei no ASP.NET. Eu quero exibir as alterações para esses dados subjacentes na página em tempo real ou quase em tempo real. Como proceder?

Meu modelo:

public class BoardGame { public int Id { get; set;} public string Name { get; set;} public string Description { get; set;} public int Quantity { get; set;} public double Price { get; set;} public BoardGame() { } public BoardGame(int id, string name, string description, int quantity, double price) { Id=id; Name=name; Description=description; Quantity=quantity; Price=price; } } 

Em vez de um database real para este exemplo, apenas armazenarei os dados na variável Application. Vou semear na minha function Application_Start do meu Global.asax.cs.

 var SeedData = new List(){ new BoardGame(1, "Monopoly","Make your opponents go bankrupt!", 76, 15), new BoardGame(2, "Life", "Win at the game of life.", 55, 13), new BoardGame(3, "Candyland", "Make it through gumdrop forrest.", 97, 11) }; Application["BoardGameDatabase"] = SeedData; 

Se eu estivesse usando o Web Forms, mostraria os dados com um repetidor.

 

Board Games

Id Name Description Quantity Price

E carregue esses dados no código por trás:

 protected void Page_Load(object sender, EventArgs e) { BoardGameRepeater.DataSource = Application["BoardGameDatabase"]; BoardGameRepeater.DataBind(); } 

Se isso fosse MVC usando o Razor, é apenas um simples foreach sobre o modelo:

 @model IEnumerable 

Board Games

@foreach (var item in Model) { }
@Html.DisplayNameFor(model => model.Id) @Html.DisplayNameFor(model => model.Name) @Html.DisplayNameFor(model => model.Description) @Html.DisplayNameFor(model => model.Quantity) @Html.DisplayNameFor(model => model.Price)
@Html.DisplayFor(modelItem => item.Id) @Html.DisplayFor(modelItem => item.Name) @Html.DisplayFor(modelItem => item.Description) @Html.DisplayFor(modelItem => item.Quantity) @Html.DisplayFor(modelItem => item.Price)

Vamos usar os Web Forms para ter uma pequena página para adicionar dados, para que possamos observar a atualização dos dados em tempo real. Eu recomendo que você crie duas janelas do navegador para que você possa ver o formulário e a tabela ao mesmo tempo.

 

Create


Id:
Name:
Description:
Quantity:
Price:

E o código por trás:

 protected void SubmitBtn_Click(object sender, EventArgs e) { var game = new BoardGame(); game.Id = Int32.Parse(Id_Tb.Text); game.Name = Name_Tb.Text; game.Description = Description_Tb.Text; game.Quantity = Int32.Parse(Quantity_Tb.Text); game.Price = Int32.Parse(Price_Tb.Text); var db = (List)Application["BoardGameDatabase"]; db.Add(game); Application["BoardGameDatabase"] = db; //only for SignalR /*var context = GlobalHost.ConnectionManager.GetHubContext(); context.Clients.All.addGame(game); */ } 

SignalR

Esta é a resposta que eu estou mais animada para compartilhar, porque representa uma implementação muito mais limpa que é leve e funciona bem no ambiente móvel de hoje (dados constritos).

Tem havido vários methods ao longo dos anos para fornecer dados em tempo real do servidor para o cliente (ou a aparência de dados empurrados). O Short Polling rápido (semelhante às minhas respostas baseadas no AJAX), o Long Polling , o Forever Frame , o Server Sent Events e o WebSockets são mecanismos de transporte diferentes usados ​​para isso. SignalR é uma camada de abstração capaz de selecionar um mecanismo de transporte apropriado com base nos resources do cliente e do servidor. A melhor parte do uso do SignalR é que é simples. Você não precisa se preocupar com o mecanismo de transporte e o modelo de programação é fácil de entender.

Eu vou definir um hub SignalR, mas apenas deixá-lo vazio.

 public class GameHub : Hub { } 

Quando eu adiciono dados ao “database”, vou executar o bit de código abaixo. Se você ler a pergunta, verá que eu a comentei no formulário “Criar”. Você vai querer descomentar isso.

 var context = GlobalHost.ConnectionManager.GetHubContext(); context.Clients.All.addGame(game); 

Aqui está o código da minha página:

 

SignalR

Id Name Description Quantity Price
<%#: Item.Id %> <%#: Item.Name %> <%#: Item.Description %> <%#: Item.Quantity %> <%#: Item.Price %>

E o código por trás:

 protected void Page_Load(object sender, EventArgs e) { BoardGameRepeater.DataSource = Application["BoardGameDatabase"]; BoardGameRepeater.DataBind(); } 

Observe o que está acontecendo aqui. Quando o servidor chama context.Clients.All.addGame(game); está executando a function atribuída a hub.client.addGame para cada cliente conectado ao GameHub. A SignalR está cuidando da conexão dos events para mim e convertendo automaticamente o meu object de game no servidor para o object de game no cliente. E o melhor de tudo, não há tráfego de rede de um lado para o outro a cada poucos segundos, então é incrivelmente leve.

Vantagens:

  • Muito leve no tráfego de rede
  • Fácil de desenvolver, mas ainda flexível
  • Não envia viewstate com o pedido
  • Não pesquisa o servidor continuamente.

Note, você poderia adicionar uma function no cliente para editedGame para empurrar dados alterados para o cliente facilmente (mesmo para excluir).

Timer / UpdatePanel

Se você estiver usando o Web Forms, você pode usar um controle chamado UpdatePanel. O UpdatePanel é capaz de atualizar seções da página de forma assíncrona, sem causar uma postagem de toda a página. Combinado com um asp: Timer, você pode atualizar a tabela quantas vezes quiser. Aqui está o código:

  

Board Games (using Update Panel)

Id Name Description Quantity Price
<%#: Item.Id %> <%#: Item.Name %> <%#: Item.Description %> <%#: Item.Quantity %> <%#: Item.Price %>

E o código por trás:

  protected void Page_Load(object sender, EventArgs e) { BoardGameRepeater.DataSource = Application["BoardGameDatabase"]; BoardGameRepeater.DataBind(); } 

Então, vamos falar sobre como isso funciona. A cada 5 segundos, o timer triggersrá um evento Tick. Isso é registrado como um servidor de postback asynchronous com o UpdatePanel, portanto ocorre um postback parcial, todo o ciclo de vida da página é executado novamente, portanto, ele recarrega os dados no evento Page Load, e todo o conteúdo do modelo de conteúdo do UpdatePanel é substituído por dados gerados pelo servidor. Vamos ver como o tráfego de rede pode parecer:

 +5s Client => Server | Run the page lifecycle, send me the contents of the ContentPanel. Server => Client | Here's the entire contents of the ContentPanel. +10s Client => Server | Run the page lifecycle, send me the contents of the ContentPanel. Server => Client | Here's the entire contents of the ContentPanel. +15s Client => Server | Run the page lifecycle, send me the contents of the ContentPanel. Server => Client | Here's the entire contents of the ContentPanel. 

Vantagens:

  • Simples de implementar. Basta adicionar um timer, um gerenciador de scripts e envolver o repetidor em um painel de atualização.

Desvantagens:

  • Pesado: O ViewState é enviado para o servidor a cada solicitação. O impacto disso pode ser aliviado se você desabilitar o ViewState, no entanto (o que você deve fazer de qualquer maneira).
  • Pesado: se os dados foram alterados ou não, você está enviando todos os dados pela linha a cada 5 segundos. Isso é um grande pedaço de largura de banda.
  • Lenta: Leva muito tempo com cada postback parcial, pois todos os dados passam pelo fio.
  • Difícil de trabalhar: quando você começa a adicionar mais funcionalidades, pode ser complicado manipular corretamente as postagens parciais corretamente.
  • Não é inteligente: mesmo que o pedido anterior não tenha terminado, continuará a ser publicado graças ao timer.
  • Não é inteligente: não é fácil lidar com a interrupção da rede.

AJAX Polling, melhor implementação

Semelhante à outra resposta baseada em AJAX, você pode pesquisar continuamente o servidor. Mas desta vez, em vez de responder com os dados para exibir, vamos responder com uma lista de IDs dos dados. O lado do cliente vai acompanhar os dados que já foram recuperados em uma matriz e, em seguida, fará uma solicitação GET separada para o servidor para os dados quando vir que uma nova ID foi adicionada.

Aqui está o nosso código de página:

 

Board Games (AJAX Polling Good)

Id Name Description Quantity Price

Aqui está o Controlador de API da Web:

 namespace RealTimeDemo.Controllers { public class GamesApiController : ApiController { [Route("api/GamesApi/GetGameIds")] public IEnumerable GetGameIds() { var data = HttpContext.Current.Application["BoardGameDatabase"] as List; var IDs = data.Select(x => x.Id); return IDs; } [Route("api/GamesApi/GetGame/{id}")] public BoardGame GetGame(int id) { var data = HttpContext.Current.Application["BoardGameDatabase"] as List; return data.Where(x => x.Id == id).SingleOrDefault(); } } 

Agora, esta é uma implementação muito melhor que a minha outra resposta baseada em AJAX e a resposta Timer / UpdatePanel. Como estamos enviando apenas os IDs a cada 5 segundos, é muito mais leve os resources da rede. Também seria bastante trivial não lidar com situações de conexão de rede ou executar algum tipo de notificação quando novos dados forem carregados, como emitir um aviso .

Vantagens

  • Não envia viewstate com pedido.
  • Não executa todo o ciclo de vida da página
  • Somente os IDs são enviados pela rede (pode ser melhorado se você enviar um registro de data e hora com a solicitação e só responder com dados que foram alterados desde o registro de data e hora) como parte da pesquisa. Apenas novos objects são recuperados do database.

Desvantagens – Ainda estamos pesquisando, gerando uma solicitação a cada poucos segundos. Se os dados não mudarem com muita frequência, você estará desnecessariamente usando a largura de banda.

Polling AJAX, implementação deficiente

Se você estiver usando MVC ou Web Forms, poderá implementar uma técnica chamada polling AJAX. Isso enviará constantemente uma solicitação AJAX ao servidor. O servidor enviará uma resposta contendo os dados mais recentes. É incrivelmente simples de implementar. Você não precisa usar o jQuery para usar o AJAX, mas fica muito mais fácil. Este exemplo usará a API da Web para a funcionalidade do lado do servidor. API da Web é semelhante ao MVC, ele usa roteamento e controladores para processar solicitações. É o substituto do ASMX Web Services .

Este é o código de formulários da web, mas é muito parecido com o código MVC, então vou omitir isso:

 

Board Games (AJAX Polling Bad)

Id Name Description Quantity Price

Isso está fazendo uma solicitação para uma API da Web. A API está retornando uma representação JSON de todos os jogos.

 public class GamesApiController : ApiController { [Route("api/GamesApi/GetGameData")] public IEnumerable GetGameData() { var data = HttpContext.Current.Application["BoardGameDatabase"] as List; return data; } } 

O resultado geral desse método é semelhante ao método Timer / UpdatePanel. Mas ele não envia nenhum dado do viewstate com a solicitação e não executa um processo de ciclo de vida de longa página. Você também não precisa dançar por aí detectando se está em um postback ou não, ou se está em um postback parcial ou não. Então eu considero isso uma melhoria em relação ao Timer / UpdatePanel.

No entanto, esse método ainda tem uma das principais desvantagens do método Timer / UpdatePanel. Você ainda está enviando todos os dados pelo fio com cada solicitação AJAX. Se você olhar para minha outra resposta baseada em AJAX, verá uma maneira melhor de implementar o polling AJAX.

Vantagens

  • Não envia viewstate com pedido.
  • Não executa todo o ciclo de vida da página

Desvantagens

  • Gera uma solicitação a cada poucos segundos
  • A resposta inclui todos os dados, mesmo que não tenham mudado