Datos en vivo con jQuery Grid, WebSockets y KnockoutJS
Existen numerosos escenarios en los que, con el único propósito de tener una aplicación efectiva o funcional, parece que el único camino a seguir es en vivo, como los datos en vivo, la actualización de los clientes en tiempo real.
Existen numerosos escenarios en los que, con el único propósito de tener una aplicación efectiva o funcional, parece que el único camino a seguir es en vivo, como los datos en vivo, la actualización de los clientes en tiempo real.
Parece que así es como se supone que se deben hacer las cosas. Los propios usuarios se han acostumbrado a las aplicaciones web dinámicas. Solo para poner en marcha su imaginación, aquí hay una captura de pantalla de la aplicación que se muestra a continuación (solo tenga en cuenta que esta quietud realmente no le hace justicia):

Sin embargo, la capacidad de entregar dicha aplicación depende de la lógica del lado del cliente, como los widgets de jQuery, o de alguna ayuda (por lo tanto, la comunicación) del servidor. El término correcto para eso sería 'sondeo' (siga esto para leer más en Wikipedia) y es el proceso en el que el cliente pregunta constantemente al servidor "¿Ya tienes algo nuevo para mí?". Y eso sucede una y otra vez. La cuestión es que produce tráfico adicional y también hace que el servidor maneje un montón de solicitudes solo para decir: "No". Eso podría convertirse en cosa del pasado (mirando los dispositivos móviles) con la tecnología push (o en pocas palabras, las notificaciones). El sistema operativo móvil admite dichos servicios (consulte Apple y Windows Phone lo llaman notificaciones automáticas, Android las llama mensajería de la nube al dispositivo) y hay algunas técnicas (como la transmisión HTML que no cerraría una conexión, sino que la mantendría abierta en un bucle). Luego también está....
WebSockets
WebSocket es una tecnología web que proporciona un dúplex completo, es decir, una comunicación bidireccional simultánea sin bloqueos. Por supuesto, vienen con API y se pueden usar tanto para el cliente como para el servidor. El protocolo en sí ha experimentado algunos cambios y está estandarizado por el IETF como RFC 6455. La capacidad de mantener la transferencia de 2 vías a través de una sola conexión TCP (un socket) lo hace muy atractivo, ya que reduciría el número de conexiones. Y, sí, siguiendo el problema explicado anteriormente, el protocolo WebSocket puede proporcionar al servidor los medios para enviar datos al cliente sin necesidad de que se soliciten o sondeen. Esto es lo que intentaría mostrarte en este blog: una forma de unir este protocolo con dos controles de cliente populares para una aplicación increíble. Ahora, dado que el protocolo ha pasado por cambios, todavía hay muchas implementaciones que no están actualizadas. Los navegadores son otro problema, ya que no todos son compatibles con el protocolo (Chrome y Firefox sí, junto con el que pronto será IE10). Esa sigue siendo una parte bastante decente de los navegadores y, si es lo suficientemente bueno para usted, puede disfrutar de los beneficios de una conexión discreta que también tiene algunos trucos de seguridad con las últimas versiones. He visto implementaciones bastante buenas de .NET o incluso Node.js y seguramente encontrará uno adecuado para el lenguaje de su elección. ¡Incluso hay un nuevo y brillante espacio de nombres llamado 'System.Net.WebSockets' en .NET Framework 4.5! Elegí una implementación de código abierto porque no requeriría una actualización del marco si no lo tiene y es compatible con la última versión (la que encontraría en los últimos navegadores Chrome o Firefox) junto con versiones anteriores. Se llama SuperWebSocket y puedes encontrarlo en CodePlex: http://superwebsocket.codeplex.com/. El lado del servidor en mi caso solo está aquí para respaldar mi demostración, así que tenga en cuenta que la implementación está lejos de ser perfecta o realmente utilizable a gran escala. El servidor está construido sobre un marco extensible, por lo que encontrará algunas referencias en mi demostración:

Esta es la sección de configuración y la inicialización:
<configuration>
<configSections>
<section name="socketServer" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine"/>
</configSections>
<socketServer>
<servers>
<server name="SuperWebSocket"
serviceName="SuperWebSocket"
ip="Any" port="2011" mode="Sync">
</server>
</servers>
<services>
<service name="SuperWebSocket"
type="SuperWebSocket.WebSocketServer, SuperWebSocket" />
</services>
</socketServer>
<!--The rest is omitted-->
Por supuesto, esto se llama solo una vez al iniciar la aplicación (el evento o cualquier equivalente que proporcione el entorno del servidor). . Además, puede conectar los eventos y manejar las conexiones y la transferencia de datos con bastante facilidad. Así es como enviamos los datos iniciales (procedentes de las clases LINQtoSQL generadas desde una base de datos Northwind local) al cliente al establecer una sesión en lugar de con la vista en sí:
private void StartWebSockServ()
{
SocketServiceConfig config = ConfigurationManager.GetSection("socketServer") as SocketServiceConfig;
// initialize with the above configuration
if (!SocketServerManager.Initialize(config))
return;
// get an instance and set up:
var socketServer = SocketServerManager.GetServerByName("SuperWebSocket") as WebSocketServer;
HttpContext.Current.Application["WebSocketPort"] = socketServer.Config.Port;
//set up the event handlers
socketServer.NewSessionConnected += new SessionEventHandler<WebSocketSession>(Controllers.HomeController.socketServer_NewSessionConnected);
socketServer.SessionClosed += new SessionEventHandler<WebSocketSession, SuperSocket.SocketBase.CloseReason>(Controllers.HomeController.socketServer_SessionClosed);
socketServer.NewDataReceived += new SessionEventHandler<WebSocketSession, byte[]>(Controllers.HomeController.socketServer_NewDataReceived);
socketServer.NewMessageReceived += new SessionEventHandler<WebSocketSession, string>(Controllers.HomeController.socketServer_NewMessageReceived);
if (!SocketServerManager.Start())
SocketServerManager.Stop();
//start ticking data broadcast
Controllers.HomeController.timer = new Timer(new TimerCallback(Controllers.HomeController.DataBroadcastOnChange));
Controllers.HomeController.timer.Change(10000, 10000);
}
Como pueden notar, realmente no tengo datos en vivo (y no me sentí haciendo un lector de Twitter / Facebook), así que residí en un evento cronometrado simple y generé algunos cambios aleatorios en los datos y les ahorraré esa parte. En una aplicación real, lo siguiente debería estar manejando algún evento en su capa de datos:
public static void socketServer_NewSessionConnected(WebSocketSession session)
{
NorthWindDataContext nw = new NorthWindDataContext();
JavaScriptSerializer serializer = new JavaScriptSerializer();
session.SendResponse(serializer.Serialize(nw.Sales_by_Categories.Take(20)));
}
Esto es básicamente todo lo que necesita para tener datos enviados desde el servidor, pero aún así puede manejar los mensajes recibidos para aprovechar la conexión bidireccional y ... Por ejemplo, guarde los cambios realizados en un cliente y envíelos a todos los demás.
El Cliente
La implementación del lado del cliente para manejar la comunicación de WebSocket es bastante simple y de alguna manera sigue la lógica en los eventos en el servidor: conectado / datos recibidos / desconectados:
public static void DataBroadcastOnChange(object state)
{
//pretend we got changes from some service while really generating randoms:
List<Sales_by_Category> changes = GetDataChanges();
//send it back to the clients
WebSocketServer socketServer = SocketServerManager.GetServerByName("SuperWebSocket") as WebSocketServer;
var sessions = socketServer.GetAllSessions();
JavaScriptSerializer serializer = new JavaScriptSerializer();
//broadcast the changes to all clients (if any)
foreach (WebSocketSession session in sessions)
{
session.SendResponse(serializer.Serialize(changes));
}
}
Tenga en cuenta que el puerto es el mismo que la configuración del servidor y que los datos que recibamos estarán en el parámetro event del evento 'onmessage'. Antes de manejar los datos, es necesario hacer algunos preparativos, ya que es el punto en el que consideramos qué hacer con nuestros datos. El objetivo es una aplicación con un cliente que simplemente se siente vivo y ya proporcionamos la plomería básica de alimentación de datos, pero solo mostrar los datos iniciales realmente no es suficiente, ¿verdad? Claro que podemos hacer eso con el poderoso jQuery Grid y probablemente no dudaría en deshacerse de los datos iniciales y mostrar el nuevo en un instante, pero esa no es una solución muy elegante y arriba ya es una pista de que solo queremos enviar los registros modificados en lugar de todo de nuevo. Así lo haremos, y podemos aprovechar algunas adiciones interesantes al conjunto de herramientas de Infragistics jQuery que viene con 12.1, una extensión para nuestros widgets de fuente de datos y cuadrícula que brinda soporte para
Knockout!
No hay que golpear: Knockout.js es una biblioteca de JavaScript que proporciona soporte para el patrón de diseño MVVM y simplifica la creación de una interfaz de usuario dinámica, lo que la hace perfecta para nuestro proyecto de demostración. Si quieres echar un vistazo a lo que es y lo que puede hacer, echa un vistazo a http://knockoutjs.com/ con un montón de ejemplos y tutoriales en vivo. En primer lugar, necesitará la biblioteca (asegúrese de obtener también el mapeo) que puede obtener del sitio o también puede descargarlo NuGet. A continuación, agregue enlaces a los scripts junto con jQuery (y UI) en su código y nuestro propio widget Loader. Ahora es el momento de crear nuestro modelo: ya hemos mencionado que estamos utilizando datos de Northwind y que es la vista 'Ventas por categoría' y este es el formato de datos que contiene:
$(document).ready(function () {
//web socket handling
if ('WebSocket' in window) {
connect('ws://localhost:2011/');
}
else {
alert("WebSockets don't seem to be supported on this browser.");
}
function connect(host) {
ws = new WebSocket(host);
ws.onopen = function () {
notify('Connected!');
};
ws.onmessage = function (evt) {
// handle data in evt.data
};
ws.onclose = function () {
notify('Socket connection was closed!!!');
};
};
});
Y luego crea su modelo de vista con una colección observable de esos elementos de la siguiente manera:
function Item(categoryID, categoryName, productName, productSales) {
//the unique key here is the prodcut name!
return {
categoryID: ko.observable(categoryID),
categoryName: ko.observable(categoryName),
productName: ko.observable(productName),
productSales: ko.observable(productSales)
};
};
Tenga en cuenta que 'jsonData' se rellena cuando obtenemos datos del servidor, más sobre eso a continuación. Hasta ahora tenemos nuestros datos y nuestro modelo y podemos empezar a crear la interfaz de usuario añadiendo
La cuadrícula
Comience configurando el widget del cargador y esta vez, excepto la cuadrícula, agregue las rutas a los dos archivos de extensión para el soporte de knockout.js:
function ItemsViewModel() {
var self = this;
self.data = ko.observableArray([]);
for (var i = 0; i < jsonData.length; i++) {
self.data.push(new Item(jsonData[i].CategoryID, jsonData[i].CategoryName, jsonData[i].ProductName, jsonData[i].ProductSales));
}
}
Tenga en cuenta que la función de actualización *es necesaria* para el soporte de eliminación. Ahora puedes usar el atributo "data-bind" de Knockout para definir la cuadrícula:
$.ig.loader({
scriptPath: "../../Scripts/js/",
cssPath: "../../Content/css/",
resources: "igGrid.Updating,extensions/infragistics.datasource.knockoutjs.js,extensions/infragistics.ui.grid.knockout-extensions.js"
});
Como puede ver, el estilo de definición de la cuadrícula sigue siendo el mismo entre paréntesis y, una vez que se ha creado, puede usar todos los métodos de API que normalmente usaría.
Enlazándolo todo junto
Lo que queda por hacer es, por supuesto, alimentar los datos a nuestro modelo y dejar que Knockout y Grid se encarguen del resto. Sin embargo, hay algunas cosas menores que hay que aclarar. En primer lugar, estoy generando cambios aleatorios como se mencionó y, dado que también quiero enviar solo los elementos modificados a través de la misma conexión, necesitaba una forma de distinguir uno de otro, así que simplemente agrego un primer registro vacío para marcar las actualizaciones (comprobando si hay una clave primaria nula, que no puede provenir de la base de datos). Sin embargo, esa es mi solución rápida y sucia, puede tener la suya propia o usar la conexión WebSocket solo para actualizaciones. En el fragmento a continuación, verá parte del conjunto de herramientas de la utilidad Knockout en acción que se usa para obtener un ID de elemento de la colección al proporcionar la clave principal y alguna API de cuadrícula utilizada para obtener la celda actualizada y hacer que parpadee un poco para que quede claro para el usuario que el valor acaba de cambiar. El evento 'onmessgae' de WebSocket ahora tiene el siguiente aspecto:
<table id="grid"
data-bind="igGrid: {
dataSource: data, width: 650, primaryKey: 'productName', autoCommit: true,
features: [ {
name: 'Updating', editMode: 'row',
},
{
name: 'Paging', pageSize: 10,
}
],
enableHoverStyles: false,
autoGenerateColumns: false,
columns: [
{key: 'categoryID', headerText: 'Category ID', width: 100, dataType: 'number'},
{key: 'categoryName', headerText: 'Category Name', width: 200, dataType: 'string'},
{key: 'productName', headerText: 'Product Name', width: 130, dataType: 'string'},
{key: 'productSales', headerText: 'Sales', width: 170, dataType: 'number'}
]}"></table>
Aparte de algunos ajustes menores como la función de notificación personalizada y algunos estilos css, esto es de lo que está hecha la aplicación. Y dado que la conexión (después de la inicial) se usa para las actualizaciones, no es un problema tan grande si se cae: según el manejo de eventos que definimos, el resultado sería:

Pero, de nuevo, la red seguirá funcionando y puede proporcionar un método para intentar volver a conectarse para que esta aplicación sea completa y funcional.
Terminando
Hemos visto cómo se puede crear una aplicación llena de movimiento, con una alimentación de datos en vivo manejada por la conexión WebSocket, un modelo KnockoutJS basado en observables y pisando eso, nuestro jQuery Grid mostrando y reflejando dinámicamente los cambios para mantener el lado del cliente siempre actualizado con los datos reales de una manera natural y con una experiencia agradable para el usuario.
Dado que las capturas de pantalla simplemente no son suficientes, aquí tengo un pequeño video que espero que llegue pronto y el proyecto de demostración una vez que nuestro primer lanzamiento para 2012 es un hecho. ¡Estén atentos!