Dados em tempo real com jQuery Grid, WebSockets e KnockoutJS
Existem vários cenários em que, com o único propósito de ter um aplicativo eficaz ou funcional, parece que o único caminho a seguir é ao vivo - como dados ao vivo, atualizando clientes em tempo real.
Existem vários cenários em que, com o único propósito de ter um aplicativo eficaz ou funcional, parece que o único caminho a seguir é ao vivo - como dados ao vivo, atualizando clientes em tempo real.
Parece que é assim que as coisas devem ser feitas. Os próprios usuários praticamente se acostumaram com aplicativos da web dinâmicos. Só para dar asas à sua imaginação, aqui está uma captura de tela do aplicativo demonstrada abaixo (lembre-se de que essa quietude realmente não faz justiça):

A capacidade de entregar esse aplicativo, no entanto, depende da lógica do lado do cliente, como os widgets jQuery ou alguma ajuda (portanto, comunicação) do servidor. O termo correto para isso seria 'enquete' (siga isso para ler mais na Wikipedia) e é o processo do cliente perguntar constantemente ao servidor "Já tem algo novo para mim?". E isso acontece repetidamente. O fato é que produz tráfego extra e também faz com que o servidor lide com um monte de solicitações apenas para dizer: "Não". Isso pode se tornar uma coisa do passado (olhando para dispositivos móveis) com a tecnologia push (ou simplesmente – notificações). O sistema operacional móvel oferece suporte a esses serviços (veja Apple e Windows Phone chamam de notificações push, o Android chama essas mensagens da nuvem para o dispositivo) e existem algumas técnicas (como streaming de HTML que não fecharia uma conexão, mas a manteria aberta em um loop). Depois, há também....
WebSockets
WebSocket é uma tecnologia da web que fornece um full-duplex - o que significa uma comunicação bidirecional simultânea sem bloqueio. Eles, é claro, vêm com API e podem ser usados tanto para cliente quanto para servidor. O protocolo em si passou por algumas mudanças e é padronizado pelo IETF como RFC 6455. A capacidade de manter a transferência de 2 vias em uma única conexão TCP (um soquete) o torna muito atraente, pois reduziria o número de conexões. E, sim, seguindo o problema explicado acima, o protocolo WebSocket pode fornecer ao servidor os meios para enviar dados ao cliente sem a necessidade de serem solicitados ou pesquisados. Isso é o que eu tentaria mostrar a você neste blog - uma maneira de juntar esse protocolo junto com dois controles de cliente populares para um aplicativo incrível. Agora, como o protocolo passou por mudanças, ainda há muitas implementações que não estão atualizadas por aí. Os navegadores são outra pegadinha, pois nem todos suportam o protocolo (Chrome e Firefox suportam, junto com o futuro IE10). Essa ainda é uma parte bastante decente dos navegadores e, se você for bom o suficiente para você, poderá aproveitar os benefícios de uma conexão discreta que também possui alguns truques de segurança com as versões mais recentes. Já vi implementações .NET ou até Node.js muito boas e certamente você encontrará uma adequada para a linguagem de sua escolha. Há até mesmo um namespace novo e brilhante chamado 'System.Net.WebSockets' no .NET Framework 4.5! Eu escolhi uma implementação de código aberto, pois não exigiria atualização de estrutura se você não a tivesse e suportasse a versão mais recente (aquela que você encontraria nos navegadores Chrome ou Firefox mais recentes) junto com versões mais antigas. Chama-se SuperWebSocket e você pode encontrá-lo no CodePlex: http://superwebsocket.codeplex.com/. O lado do servidor, no meu caso, está aqui apenas para apoiar minha demonstração, então tenha isso em mente que a implementação está longe de ser perfeita ou realmente utilizável em grande escala. O servidor é construído em uma estrutura extensível, então você encontrará algumas referências em minha demonstração:

Aqui está a seção de configuração e a inicialização:
<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-->
É claro que isso é chamado apenas uma vez na inicialização do aplicativo (o evento ou qualquer equivalente que o ambiente do servidor forneça). . Além disso, você pode conectar os eventos e lidar com conexões e transferência de dados com bastante facilidade. Veja como enviamos os dados iniciais (provenientes das classes LINQtoSQL geradas de um banco de dados Northwind local) para o cliente ao estabelecer uma sessão em vez de com a exibição em si:
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 você pode notar, eu realmente não tenho dados ao vivo (e não me senti fazendo um leitor do Twitter / Facebook), então residi em um evento cronometrado simples e gerei algumas mudanças aleatórias nos dados e vou poupá-lo dessa parte. Em um aplicativo real, o seguinte deve estar manipulando algum evento em sua camada de dados:
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)));
}
Isso é basicamente tudo o que você precisa para ter os dados enviados do servidor, mas ainda assim você pode lidar com a mensagem recebida para aproveitar a conexão bidirecional e... Por exemplo, salve as alterações feitas em um cliente e envie-as para todos os outros!
O Cliente
A implementação do lado do cliente para lidar com a comunicação do WebSocket é bastante simples e segue um pouco a lógica dos eventos no servidor – conectado / dados recebidos / 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));
}
}
Observe que a porta é a mesma que a configuração do servidor e que os dados que recebemos estarão no parâmetro event do evento 'onmessage'. Antes de lidar com os dados, alguns preparativos são necessários, pois é o ponto em que consideramos o que fazer com nossos dados. O objetivo é um aplicativo com um cliente que pareça vivo e já fornecemos os dados básicos que alimentam o encanamento, mas apenas exibir os dados iniciais não é suficiente, certo? Claro que podemos fazer isso com o poderoso jQuery Grid e provavelmente não hesitaria em abandonar os dados iniciais e exibir o novo em um piscar de olhos, mas essa não é uma solução muito graciosa e acima já é uma dica de que queremos apenas enviar os registros alterados em vez de tudo novamente. Portanto, devemos aproveitar algumas adições interessantes ao conjunto de ferramentas Infragistics jQuery que vem com 12.1 - uma extensão para nossos widgets de fonte de dados e grade que fornece suporte para
Knockout!
Sem batidas envolvidas – Knockout.js é uma biblioteca JavaScript que fornece suporte para o padrão de design MVVM e simplifica a criação de interface do usuário dinâmica e isso a torna perfeita para nosso projeto de demonstração. Se você quiser dar uma olhada no que é e o que pode fazer, confira http://knockoutjs.com/ com muitos exemplos e tutoriais ao vivo. Em primeiro lugar, você precisará da biblioteca (certifique-se de pegar também o Mapeamento), que você pode pegar do site ou pode baixá-lo NuGet também. Em seguida, adicione links para os scripts junto com jQuery (e interface do usuário) em seu código e nosso próprio widget Loader. Agora é hora de criar nosso modelo – já mencionamos que estamos usando dados da Northwind e essa é a visualização 'Vendas por categoria' e este é o formato de dados que ele contém:
$(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!!!');
};
};
});
E então você cria seu modelo de exibição com uma coleção observável desses itens da seguinte forma:
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)
};
};
Lembre-se de que o 'jsonData' é preenchido quando obtemos dados do servidor, mais sobre isso abaixo. Até agora, temos nossos dados e nosso modelo e podemos começar a criar a interface do usuário adicionando
A grade
Comece definindo o widget do carregador e, desta vez, exceto a grade, adicione os caminhos aos dois arquivos de extensão para o suporte 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));
}
}
Observe que o recurso de atualização *é necessário* para o suporte a nocaute. Agora você pode usar o atributo "data-bind" do Knockout para definir o Grid:
$.ig.loader({
scriptPath: "../../Scripts/js/",
cssPath: "../../Content/css/",
resources: "igGrid.Updating,extensions/infragistics.datasource.knockoutjs.js,extensions/infragistics.ui.grid.knockout-extensions.js"
});
Como você pode ver, o estilo de definição de grade permanece o mesmo entre colchetes e, depois de criado, você pode usar todos os métodos de API que normalmente usaria.
Juntando tudo
O que resta a fazer é, claro, alimentar os dados para o nosso modelo e deixar o Knockout e o Grid lidarem com o resto. No entanto, há algumas pequenas coisas a serem esclarecidas. Em primeiro lugar, estou gerando alterações aleatórias conforme mencionado e, como também quero enviar apenas os itens alterados pela mesma conexão, precisava de uma maneira de distinguir um do outro, então apenas adiciono um primeiro registro vazio para marcar as atualizações (verificando se há chave primária nula, que não pode vir do banco de dados). No entanto, essa é minha solução rápida e suja, você pode ter a sua própria ou usar a conexão WebSocket apenas para atualizações. No trecho abaixo, você verá algumas das ferramentas do utilitário Knockout em ação usadas para obter um ID de item da coleção, fornecendo a chave primária e algumas APIs de grade usadas para obter a célula atualizada e fazê-la piscar um pouco para deixar claro para o usuário que o valor acabou de ser alterado. O evento 'onmessgae' do WebSocket agora se parece com isso:
<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>
Além de alguns pequenos ajustes, como função de notificação personalizada e alguns estilos css, é disso que o aplicativo é feito. E como a conexão (após a inicial) é usada para atualizações, não é um problema tão grande se ela cair – com base no tratamento de eventos, definimos que o resultado seria:

Mas, novamente, a grade permanecerá funcional e você pode usar um método para tentar se reconectar para tornar este aplicativo completo e funcional.
Conclusão
Vimos como você pode criar um aplicativo cheio de movimento - com feed de dados ao vivo manipulado pela conexão WebSocket, um modelo KnockoutJS baseado em observáveis e pisando nele, nosso jQuery Grid exibindo e refletindo dinamicamente as mudanças para manter seu lado do cliente sempre atualizado com os dados reais de uma forma natural e com uma experiência agradável para o usuário!
Como as capturas de tela simplesmente não são suficientes, aqui tenho um pequeno vídeo, espero que em breve e o projeto de demonstração assim que nosso primeiro lançamento for 2012 seja um fato. Fique ligado!