Desenvolvimento - C#
Sistema de troca de mensagens com AngularJS e SignalR
Veja nesse artigo como trabalhar com SignalR e AngularJS através de um exemplo clássico de aplicação em tempo real, que é o desenvolvimento de um chat.
por Edison OliveiraSignalR é um framework para desenvolvedores que utilizam a plataforma ASP.NET e que simplifica muito o desenvolvimento de aplicações web em tempo real (Real Time), que permite ao servidor enviar conteúdo instantaneamente aos clientes conectados, sem que o mesmo receba uma requisição desses clientes. Como exemplos de aplicações web em tempo real podemos citar chats, feed de notícias, caixas de entrada de e-mail, dashboards e apuração de votos. Todas essas aplicações têm em comum que, uma vez que o cliente abre o browser e se conecta ao servidor, ele não precisa ficar atualizando a página toda vez que ele quiser ver se há um novo e-mail em sua caixa de entrada, ou se há uma nova notícia no seu feed do Facebook. Se um novo e-mail é enviado a ele, sua caixa de entrada é imediatamente atualizada, sem que ele precise fazer nada, já que o trabalho é feito pelo servidor. É o servidor que, ao receber a mensagem de e-mail, dispara a ação de atualizar a caixa de entrada do cliente, se ele estiver conectado, ou seja, para esse exemplo, estar com o browser aberto na página de e-mail.
SignalR torna essa tarefa muito simples, fornecendo uma API que encapsula a comunicação entre cliente e servidor via chamadas remotas de procedimentos (RPC), expondo ao desenvolvedor apenas a preocupação do que será transmitido (broadcast) e não como.
Arquitetura do SignalR
Os pacotes enviados pelo SignalR são transmitidos geralmente via WebSocket, que é um protocolo baseado no TCP, que permite a comunicação bidirecional. Por que geralmente? Porque websocket requer que servidor e cliente tenham suporte a HTML5 e que o servidor esteja usando Windows Server 2012 ou Windows 8 e .NET Framework 4.5. Se o servidor não atender a esses requisitos, SignalR irá utilizar outros protocolos (foreverFrame, serverSentEvents ou longPolling) para efetuar a transmissão dos pacotes.
As conexões entre cliente e servidor podem ser de dois tipos: Persistent Connections e Hubs.
As persistent connections formam um modelo de mais baixo nível de programação, mas em contrapartida, oferecem mais flexibilidade ao desenvolvedor, como por exemplo, definir o protocolo de comunicação.
Os Hubs, por sua vez, são de alto nível de programação, deixando o desenvolvedor se preocupar apenas com o que e quando transmitir mensagens.
A Figura 1 ilustra a arquitetura do SignalR.
Figura 1. Arquitetura do SignalR
Nossa Aplicação Demo
Nossa demo será uma aplicação web com um único form em que o usuário escreve e posta uma mensagem e também visualiza todas as mensagens postadas. O primeiro passo é adicionar os pacotes AngularJS e SignalR via Nuget no nosso projeto no Visual Studio, conforme mostram as Figuras 2 e 3.
Figura 2. Adicionando AngularJS ao projeto.
Figura 3. Adicionando SignalR ao projeto.
Para iniciar o desenvolvimento propriamente, incluiremos nosso HUB (conforme descrito previamente, HUB é um tipo de conexão no SignalR), conforme a Figura 4.
Figura 4. Adicionando hub
No código do Hub incluiremos o método SendMessage, que será invocado cada vez que um usuário enviar a mensagem. Fazendo uso da propriedade All, do objeto Clients, incluímos o método que fará a propagação dessa mensagem aos clientes conectados. O objeto Clients já é inerente à classe Hub e, portanto, não é necessário instanciá-lo. Na Listagem 1 vemos o código da nossa classe MyHub.
Listagem 1. Classe MyHub
[HubName("HubMessage")] public class MyHub : Hub { public void SendMessage(string remetente, string destinatario, string message) { Clients.All.messageAdded(remetente, destinatario, message); } }
Através do IntelliSense pode-se perceber que a propriedade All não implementa, nativamente, o método messageAdded. Isso é porque estamos dizendo ao SignalR que as aplicações clientes (no caso, AngularJS) vão estar aguardando a chamada desse método, como veremos a seguir.
O próximo passo é adicionar uma classe startup OWIN, que irá configurar as rotas do SignalR quando a aplicação iniciar. A Figura 5 exibe como adicionar a classe.
Figura 5. Adicionando a OWIN Startup Class
Dentro da classe OWIN apenas incluiremos a chamada ao método MapSignalR no método Configuration. E a Listagem 2 exibe a classe OWIN.
Listagem 2. Classe OWIN
[assembly: OwinStartup(typeof(SignalR.Startup))] namespace SignalR { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } }Iniciaremos agora a implementação da aplicação cliente no AngularJS. Após incluir no projeto um arquivo JavaScript, nomeado App.js, definiremos nossa aplicação como messageBox, conforme a Listagem 3.
Listagem 3. Definindo MessageBOX
(function () { var app = angular.module('messageBox', []); })()
Dentro do nosso app definiremos os dois elementos que construirão a aplicação cliente: a controller e a factory.
Na factory define-se a conexão ao Hub e atribui-se um delegate ao método que será chamado pelo servidor. Na controller temos o método que inclui a mensagem e envia ao servidor, além do método que recebe a resposta do servidor, que pode ter sido chamado por qualquer cliente. O código completo do arquivo App.js é definido na Listagem 4.
Listagem 4. Arquivo App.js
(function () { var app = angular.module('messageBox', []); app.value('$', $); app.factory('noteService', ['$', '$rootScope', function ($, $rootScope) { var proxy; var connection; return { connect: function () { connection = $.hubConnection(); proxy = connection.createHubProxy('HubMessage'); connection.start(); proxy.on('messageAdded', function (remetente, destinatario, message) { $rootScope.$broadcast('messageAdded', remetente, destinatario, message); }); }, isConnecting: function () { return connection.state === 0; }, isConnected: function () { return connection.state === 1; }, connectionState: function () { return connection.state; }, sendMessage: function (remetente, destinatario, message) { proxy.invoke('SendMessage', remetente, destinatario, message); }, } }]); app.controller('messageController', function ($scope, noteService) { noteService.connect(); $scope.messages = []; $scope.$on('messageAdded', function (event, remetente, destinatario, message) { var mensagem = { de: remetente, para: destinatario, mensagem: message }; $scope.messages.push(mensagem); $scope.$apply(); }); $scope.sendMessage = function () { noteService.sendMessage($scope.remetente, $scope.destinatario, $scope.mensagem); }; }); })()
O método sendMessage da factory noteService fará a chamada ao servidor e enviará a mensagem postada pelo usuário. No servidor, por sua vez, a mensagem é enviada aos clientes, invocando o método messageAdded, presente na controller que estará em todos os clientes.
Por fim, adicionaremos nosso form terá três campos: um para o remetente, um para o destinatário e um para o texto da mensagem. Todos os campos do form estarão conectados à controller através do atributo ng-Model. O botão que posta a mensagem invoca o método sendMessage, também implementado na controller. Por fim, há uma div que será repetida para cada mensagem enviada, contendo o destinatário, o remetente e o texto da mensagem.
No form faremos referência aos arquivos JavaScript utilizados: AngularJS, SignalR, jQuery e nosso App. O código completo do form é descrito na Listagem 5.
Listagem 5. Form principal
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" ng-app="messageBox"> <head> <title></title> <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" /> <script src="Scripts/jquery-1.6.4.js"></script> <script src="Scripts/jquery.signalR-2.2.0.js"></script> <script src="Scripts/angular.js"></script> <script src="App.js"></script> </head> <body ng-controller="messageController"> <form> <div class="form-group"> <label class="col-sm-3 control-label">Remetente</label> <div class="col-sm-9"> <input type="text" class="form-control" ng-model="remetente"> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">Destinatário</label> <div class="col-sm-9"> <input type="text" class="form-control" ng-model="destinatario"> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">Mensagem</label> <div class="col-sm-9"> <textarea class="form-control" rows="3" ng-model="mensagem"></textarea> </div> </div> <div class="col-sm-8"> <button type="submit" class="btn btn-default" ng-click="sendMessage()">Enviar</button> </div> </form> <br /> <div ng-repeat="item in messages"> <div class="col-sm-10 table table-bordered"> <h3>{{item.mensagem}}</h3> <br /> De: {{item.de}} <br /> Para: {{item.para}} </div> </div> </body> </html>
A Figura 6 mostra a aplicação funcionando. No lado esquerdo temos uma instância do Internet Explorer, onde a usuária Márcia escreve ao usuário Paulo, que está conectado na instância do Google Chrome, à direita. A cada vez que um usuário posta uma mensagem e clica em enviar, a mensagem é automaticamente exibida ao destinatário, sem que ele tenha que atualizar seu browser.
Figura 6. Aplicação funcionando
O fluxo da informação é o seguinte:
- o usuário clica no botão Enviar e dispara o método sendMessage, presente na controller messageController;
- o Controller invoca o método sendMessage da factory noteService;
- a Factory noteService invoca o método sendMessage no servidor, presente no HUB. É nesse ponto que o SignalR é invocado;
- o HUB invoca o método messageAdded da propriedade All do objeto Clients. Esse é o momento exato em que acontece a propagação do pacote para todos os clientes conectados;
- o método $scope.$on da controller messageController terá sido disparado remotamente pelo SignalR e incluirá a mensagem no array $scope.messages;
- o objeto $scope.messages é o objeto que alimenta a lista de mensagens do formulário. Assim que a nova mensagem é incluída no array, através do método push, uma nova linha é incluída na lista e o browser é automaticamente atualizado.
O resultado dessa integração entre AngularJS e SignalR é uma experiência de uso muito rica aos usuários. E isso pode ser alcançado com poucas linhas de código.
Caso seu projeto não funcione, recomendamos o download original.