Aplicações em tempo real com ASP.NET Core SignalR

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.

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

SignalR

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());
});
C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

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

Deixe seu comentário

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.