No meu artigo sobre streaming API, citei que no ASP.NET Core a entrega de dados em tempo real pode ser realizada via SignalR, que é uma biblioteca criada para este fim. Neste artigo vamos conhecê-la em detalhes.
Na época que isso aqui era só mato…
Os protocolos base da internet são baseados em um modelo cliente-servidor síncrono. O cliente envia uma solicitação, o servidor a processa e retorna uma resposta, finalizando a conexão. Neste cenário, criar uma aplicação que possua/necessite de comunicação em tempo real, com acesso assíncrono de um ou mais usuários é algo extremamente complexo.
Procurando contornar isso, ao longo do tempo foram surgindo tecnologias/técnicas que permitem a implementação de comunicação em tempo real, como:
- WebSockets;
- Long Polling;
- Server-Sent Events.
Entretanto, cada uma possui sua complexidade e limitação técnica. Felizmente, para os desenvolvedores ASP.NET surgiu uma nova opção, o SignalR.
Curso C# - Entity Framework Core ORM
Conhecer o cursoSignalR
O SignalR é uma biblioteca open source criada em 2011 por David Fowler e Damian Edwards com o intuito de facilitar a implementação de aplicações em tempo real no ASP.NET.
Na época da sua criação, o WebSocket havia acabado de ser padronizado, não era popular e não estava disponível em todos os navegadores. Assim, para desenvolver uma aplicação em tempo real, o desenvolvedor precisava decidir se a limitaria apenas a alguns navegadores ou utilizava mais de uma tecnologia/técnica.
O SignalR foi criado para resolver este problema. Nos bastidores, ele define qual é o melhor tipo de protocolo, tecnologia ou técnica que a conexão irá utilizar baseado no que o cliente e servidor suportam. Com isso, fornece um endpoint (chamado de Hub) que pode enviar e receber mensagens em tempo real. Como é fácil de ser utilizado, rapidamente o SignalR se tornou a principal opção para se desenvolver aplicações em tempo real no ASP.NET.
Hubs
Para abstrair a comunicação entre clientes e servidores, o SignalR fornece um pipeline de auto nível chamado Hub. O SignalR lida com o envio através dos limites da máquina automaticamente, permitindo que clientes chamem os métodos no servidor e vice-versa. É possível passar parâmetros fortemente tipados para os métodos, o que habilita model binding.
Para realizar esta comunicação, o SignalR fornece dois protocolos: um baseado em JSON e outro binário, baseado em MessagePack. O MessagePack geralmente cria mensagens menores, em comparação com o JSON. O navegador precisa suportar XHR level 2 para ter suporte ao MessagePack.
Colocando a mão na massa
Para exemplificar o uso do SignalR, vamos alterar a aplicação demostrada no artigo de streaming API.
Na versão 3.0 do .NET Core, o SignalR foi adicionado na biblioteca padrão, assim para utilizá-lo não é necessário adicionar nenhuma biblioteca externa ao projeto.
Inicialmente iremos criar um Hub. Um Hub é uma classe que herda a classe Hub
do namespace Microsoft.AspNetCore.SignalR
:
public class StreamingHub: Hub
{
//Vazia porque a comunicação será realizada do servidor para o cliente
}
Como neste exemplo quem irá gerar os eventos é o servidor, não é será necessário definir nenhum método dentro da classe.
Para habilitar o SignalR no projeto, é necessário adicioná-lo no método ConfigureServices
da classe Startup
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSignalR();
}
Nesta mesma classe, no método Configure
é necessário indicar o endpoint do hub que acabamos de definir:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<StreamingHub>("/streaminghub");
});
}
Por fim, no controller é indicado que o Hub será recebido via injeção de dependência:
private readonly IHubContext<StreamingHub> _streaming;
public TodoController(IHubContext<StreamingHub> streaming) => _streaming = streaming;
Isso permitirá o envio de mensagens pelo servidor.
No artigo anterior foi definido que a cada ação do controller uma mensagem era enviada para o endpoint de streaming. Isso é feito no método WriteOnStream
:
private async Task WriteOnStream(Item data, string action)
{
string jsonData = string.Format("{0}\n", JsonSerializer.Serialize(new { data, action }));
foreach (var client in _clients)
{
await client.WriteAsync(jsonData);
await client.FlushAsync();
}
}
Como o nosso Hub irá executar o mesmo procedimento, podemos enviar sua mensagem neste mesmo método:
private async Task WriteOnStream(Item data, string action)
{
string jsonData = string.Format("{0}\n", JsonSerializer.Serialize(new { data, action }));
//Utiliza o Hub para enviar uma mensagem para ReceiveMessage
await _streaming.Clients.All.SendAsync("ReceiveMessage", jsonData);
foreach (var client in _clients)
{
await client.WriteAsync(jsonData);
await client.FlushAsync();
}
}
Definindo o cliente
Para que um cliente se conecte ao endpoint do hub é necessário utilizar uma biblioteca apropriada. Apenas assim, o SignalR conseguirá determinar qual tipo de protocolo utilizar durante a conexão. Felizmente a Microsoft criou bibliotecas clientes para as mais variadas linguagens: JavaScript, Java, C#, entre outras.
Neste exemplo iremos utilizar a biblioteca do JavaScript, que pode ser obtida via NPM (pacote @aspnet/signalr
), mas aqui irei utilizar a versão CDNJS:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Mensagens</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<ul id="messagesList"></ul>
<script src='https://cdnjs.cloudflare.com/ajax/libs/aspnet-signalr/1.1.4/signalr.min.js'></script>
<script src='main.js'></script>
</body>
</html>
Por fim, é necessário definir no JavaScript a comunicação com o endpoint (arquivo main.js):
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/streaminghub").build();
connection.on("ReceiveMessage", function (message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var li = document.createElement("li");
li.textContent = msg;
document.getElementById("messagesList").appendChild(li);
});
connection.start().then(function () {
var li = document.createElement("li");
li.textContent = "Connetado!";
document.getElementById("messagesList").appendChild(li);
}).catch(function (err) {
return console.error(err.toString());
});
Note que inicialmente é criada uma conexão:
var connection = new signalR.HubConnectionBuilder().withUrl("/streaminghub").build();
Esta conexão ficará “ouvindo” o evento ReceiveMessage
:
connection.on("ReceiveMessage", function (message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var li = document.createElement("li");
li.textContent = msg;
document.getElementById("messagesList").appendChild(li);
});
E quando a conexão for aberta isso é informado:
connection.start().then(function () {
var li = document.createElement("li");
li.textContent = "Connetado!";
document.getElementById("messagesList").appendChild(li);
}).catch(function (err) {
return console.error(err.toString());
});
Curso C# - Entity Framework Core ORM
Conhecer o cursoAgora ao executar a aplicação e realizar ações no endpoint Todo, o SignalR informará os clientes conectados no Hub:
Outro uso comum do SignalR é na criação de um chat. No próximo artigo abordarei mais algumas características dele e apresentarei o exemplo de um chat.
Você pode obter o código da aplicação demostrado neste arquivo no meu Github.