Cómo implementar datos en tiempo real para una página web

(Esto tiene la intención de ser una pregunta de estilo Q / A, destinada a ser un recurso recurrente para las personas que hacen preguntas similares. Mucha gente parece tropezar con la mejor manera de hacerlo porque no conocen todas las opciones Muchas de las respuestas serán ASP.NET específicas, pero AJAX y otras técnicas tienen equivalentes en otros marcos, como socket.io y SignalR.

Tengo una tabla de datos que he implementado en ASP.NET. Quiero mostrar los cambios a estos datos subyacentes en la página en tiempo real o casi en tiempo real. ¿Cómo lo hago?

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

En lugar de una base de datos real para este ejemplo, solo voy a almacenar los datos en la variable Aplicación. Voy a sembrar en mi función Application_Start de mi 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; 

Si estuviera usando Formularios web, mostraría los datos con un repetidor.

 

Board Games

Id Name Description Quantity Price

Y cargue esos datos en el código detrás:

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

Si se tratara de MVC con Razor, es solo un simple foreach sobre el 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)

Usemos formularios web para tener una pequeña página para agregar datos para poder ver la actualización de los datos en tiempo real. Te recomiendo que crees dos ventanas de navegador para que puedas ver el formulario y la tabla al mismo tiempo.

 

Create


Id:
Name:
Description:
Quantity:
Price:

Y el código detrá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 es la respuesta que estoy más emocionada de compartir, ya que representa una implementación mucho más limpia, liviana y que funciona bien en el entorno móvil actual (constreñimiento de datos).

Ha habido varios métodos a lo largo de los años para proporcionar un empuje de datos “en tiempo real” desde el servidor al cliente (o la aparición de datos de inserción). Rapid Short Polling (similar a mis respuestas basadas en AJAX), Long Polling , Forever Frame , Server Sent Events y WebSockets son diferentes mecanismos de transporte utilizados para lograr esto. SignalR es una capa de abstracción capaz de seleccionar un mecanismo de transporte adecuado en función de las capacidades del cliente y del servidor. La mejor parte de usar SignalR es que es simple. No tiene que preocuparse por el mecanismo de transporte, y el modelo de progtwigción es fácil de entender.

Definiré un concentrador SignalR, pero lo dejo vacío.

 public class GameHub : Hub { } 

Cuando agregue datos a la “base de datos”, voy a ejecutar el siguiente código. Si lees la pregunta, verás que la comenté en el formulario “crear”. Querrá descomentar eso.

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

Aquí está el código de mi página:

 

SignalR

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

Y el código detrás:

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

Observe lo que está sucediendo aquí. Cuando el servidor llama a context.Clients.All.addGame(game); está ejecutando la función que se ha asignado a hub.client.addGame para cada cliente que está conectado a GameHub. SignalR se encarga de cablear los eventos para mí y de convertir automáticamente mi objeto de game en el servidor al objeto del game en el cliente. Y lo mejor de todo es que no hay tráfico de red cada pocos segundos, por lo que es increíblemente liviano.

Ventajas:

  • Muy ligero en el tráfico de la red
  • Fácil de desarrollar, pero aún flexible
  • No envía viewstate con la solicitud
  • No sondea el servidor continuamente.

Tenga en cuenta que puede agregar una función en el cliente para editedGame para editedGame datos modificados al cliente fácilmente (lo mismo para eliminar).

Timer / UpdatePanel

Si usa formularios web, puede usar un control llamado UpdatePanel. El UpdatePanel es capaz de actualizar secciones de la página de forma asincrónica, sin causar una devolución de datos de toda la página. Combinado con un temporizador asp: puede hacer que la tabla se actualice tantas veces como desee. Aquí está el código:

  

Board Games (using Update Panel)

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

Y el código detrás:

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

Entonces, hablemos de cómo funciona esto. Cada 5 segundos, el temporizador disparará un evento Tick. Esto se registra como un servidor de devolución de datos asincrónico con UpdatePanel, por lo que se produce una devolución de datos parcial, todo el ciclo de vida de la página se ejecuta nuevamente, así que vuelve a cargar los datos en el evento Carga de página, luego se reemplaza todo el contenido de la plantilla de contenido de UpdatePanel datos generados desde el servidor. Veamos cómo se vería el tráfico de red:

 +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. 

Ventajas:

  • Simple de implementar. Simplemente agregue un temporizador, un administrador de scripts y ajuste el repetidor en un panel de actualización.

Desventajas:

  • Pesado: ViewState se envía al servidor con cada solicitud. El impacto de esto se puede aligerar si deshabilita ViewState (sin embargo, debe hacerlo de todos modos).
  • Pesado: Si los datos han cambiado o no, está enviando todos los datos a través de la línea cada 5 segundos. Eso es una gran parte del ancho de banda.
  • Lento: lleva mucho tiempo con cada devolución de datos parcial, ya que todos los datos pasan por el cable.
  • Resistente para trabajar con: Cuando comienzas a agregar más funcionalidad, puede ser complicado manejar correctamente las devoluciones parciales de manera adecuada.
  • No inteligente: incluso si la solicitud anterior no finalizó, continuará publicando gracias al temporizador.
  • No inteligente: no es una forma fácil de manejar la interrupción de la red.

AJAX Polling, mejor implementación

Similar a la otra respuesta basada en AJAX, puede sondear continuamente el servidor. Pero esta vez, en lugar de responder con los datos para mostrar, vamos a responder con una lista de ID de los datos. El lado del cliente va a hacer un seguimiento de los datos que ya ha recuperado en una matriz, luego hará una solicitud GET por separado para el servidor en busca de datos cuando vea que se ha agregado una nueva ID.

Aquí está nuestro código de página:

 

Board Games (AJAX Polling Good)

Id Name Description Quantity Price

Aquí está el controlador de la API 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(); } } 

Ahora, esta es una implementación mucho mejor que mi otra respuesta basada en AJAX y la respuesta Timer / UpdatePanel. Dado que solo enviamos las identificaciones cada 5 segundos, se trata de una carga mucho más ligera para los recursos de la red. También sería bastante trivial no manejar situaciones de conexión de red, o ejecutar algún tipo de notificación cuando se carguen nuevos datos, como arrojar un noty .

Ventajas

  • No envía viewstate con solicitud.
  • No ejecuta todo el ciclo de vida de la página
  • Solo se envían ID a través del cable (podrían mejorar si envió una marca de tiempo con la solicitud, y solo respondió con datos modificados desde la marca de tiempo) como parte de la votación. Solo se recuperan objetos nuevos de la base de datos.

Desventajas: todavía estamos sondeando, generando una solicitud cada pocos segundos. Si los datos no cambian con frecuencia, estás usando innecesariamente ancho de banda.

AJAX Polling, implementación deficiente

Si está utilizando MVC o Web Forms, puede implementar una técnica llamada sondeo AJAX. Esto enviará constantemente una solicitud AJAX al servidor. El servidor enviará una respuesta que contiene los datos más recientes. Es increíblemente simple de implementar. No tiene que usar jQuery para usar AJAX, pero lo hace mucho más fácil. Este ejemplo va a usar la API web para la funcionalidad del lado del servidor. Web API es similar a MVC, usa enrutamiento y controladores para procesar solicitudes. Es el reemplazo de los servicios web ASMX .

Este es el código de formularios web, pero es muy similar al código MVC, así que voy a omitir eso:

 

Board Games (AJAX Polling Bad)

Id Name Description Quantity Price

Esto es hacer una solicitud a una API web. La API está devolviendo una representación JSON de todos los juegos.

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

El resultado general de este método es similar al método Timer / UpdatePanel. Pero no envía datos de viewstate con la solicitud y no ejecuta un proceso de ciclo de vida largo de la página. Tampoco tienes que bailar para detectar si estás en una devolución de datos o no, o si tienes una devolución de datos parcial o no. Así que considero esto una mejora sobre Timer / UpdatePanel.

Sin embargo, este método todavía tiene una de las principales desventajas del método Timer / UpdatePanel. Todavía está enviando todos los datos a través del cable con cada solicitud de AJAX. Si miras mi otra respuesta basada en AJAX, verás una mejor manera de implementar el sondeo AJAX.

Ventajas

  • No envía viewstate con solicitud.
  • No ejecuta todo el ciclo de vida de la página

Desventajas

  • Genera una solicitud cada pocos segundos
  • La respuesta incluye todos los datos, incluso si no ha cambiado