Construindo uma Aplicação em Tempo Real com WebSockets: Integrando Asp.Net, Sql Server e Angular

Yuri Nabaia
TOTVS Developers
Published in
9 min readAug 31, 2023

Em um mundo cada vez mais conectado e orientado a dados, as aplicações modernas precisam oferecer experiências em tempo real para os usuários. Imagine uma aplicação na qual as atualizações são entregues aos seus usuários assim que ocorrem, sem a necessidade de atualizar a página manualmente. Isso é possível através do uso inteligente de tecnologias como WebSockets, que permite uma comunicação bidirecional eficiente entre servidores e clientes.

Neste artigo, exploraremos a criação de uma aplicação em tempo real, na qual usaremos o SignalR para estabelecer a comunicação por WebSockets. Vamos aproveitar a potência do Sql Serve como nosso banco de dados e o Angular como o framework do lado do cliente. Com essa combinação, seremos capazes de criar um fluxo contínuo de dados entre o servidor e o cliente, proporcionando uma experiência dinâmica e atualizações instantâneas sempre que houver mudanças nos dados.

Abordaremos desde os conceitos básicos até a implementação prática, passando pelo desenvolvimento do servidor usando o SignalR para escutar as alterações em tempo real no banco de dados Sql Serve com uso do SqlDependency. Além disso, exploraremos como o Angular pode ser configurado para receber esses dados e atualizar a interface do usuário de forma reativa.

Ao final deste artigo, você terá uma compreensão sólida de como integrar tecnologias-chave para construir aplicações em tempo real envolventes. Vamos mergulhar fundo nesse processo emocionante de criação e aprendizado. Preparado para trazer uma nova dimensão de interatividade às suas aplicações? Então, vamos começar!

O monitoramento em tempo real de mudanças no banco de dados requer uma análise contextual. Primeiramente, é essencial compreender o ambiente no qual essa funcionalidade será aplicada. Existem várias abordagens para implementar essa monitorização, e considerando a eficiência e a otimização do uso, os bancos de dados NoSQL frequentemente se destacam devido à sua agilidade e eficácia. Por outro lado, a escolha por bancos de dados relacionais depende do cenário específico no qual serão empregados. Em certas situações, os dados podem estar exclusivamente armazenados em um banco de dados relacional e não podem ser migrados para um banco NoSQL. Isso é particularmente relevante para sistemas legados que ainda necessitam das informações do banco de dados relacional. Consequentemente, a decisão a ser tomada depende inteiramente do contexto em questão, mas o recomendado é usar banco NoSql para esta finalidade.

Neste artigo, abordarei a configuração do SQL Server para possibilitar a integração em tempo real. Dessa forma, você estará pronto para utilizar o SqlDependency que foi lançado em 2005 a parti do .Net Framework 2.0. Vamos explorar as etapas necessárias para estabelecer essa funcionalidade vital que permite a detecção ágil de mudanças no banco de dados.

Para habilitar o SqlDependency é bem simples devemos apenas habilitar o Service Broker do SqlServe

ALTER DATABASE NomeDoSeuBancoDados SET ENABLE_BROKER

Para ter mais informações sobre habilitar o Broker do SqlServe segue a documentação da Microsfot:

https://learn.microsoft.com/pt-br/troubleshoot/system-center/scom/troubleshoot-sql-server-service-broker-issues

Antes de mergulharmos na aplicação em Asp.Net, é fundamental compreender a biblioteca SignalR.

Resumidamente, o SignalR é uma ferramenta essencial para o desenvolvimento web que simplifica a criação de comunicação em tempo real entre servidores e clientes. Essa biblioteca utiliza tecnologias como WebSockets, long polling e outras abordagens para estabelecer conexões bidirecionais. Com isso, os servidores podem enviar atualizações em tempo real de maneira eficiente aos clientes. O SignalR encontra destaque em cenários que exigem notificações instantâneas, chats ao vivo, painéis de monitoramento e outras situações onde a sincronização em tempo real é crucial. O que o torna ainda mais valioso é a capacidade de simplificar a complexidade da comunicação assíncrona, fornecendo uma abstração poderosa para desenvolvedores que buscam criar experiências interativas e dinâmicas na web.

Primeiro, devemos baixar o SIGNALR no Asp.Net. Neste artigo, irei utilizar o .Net 6.0, e irei usar a biblioteca do AspNetCore.SignalR.Core

Neste artigo irei usar a biblioteca abaixo:
https://www.nuget.org/packages/Microsoft.AspNetCore.SignalR.Client

Após instalar a biblioteca, é necessário configurar o “Program” do ASP.NET, é o ponto de entrada para o aplicativo. É preciso configurar o Services.AddSignalR e a rota MapHub<ChatHubNotification>(“/chat-hub”).


using WebApplication1;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

//Adicionar o SignalR
builder.Services.AddSignalR();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();


//Adicionar a rota do Hub que será criado
app.MapHub<ChatHubNotification>("/chat-hub");

app.Configuration.GetConnectionString("DefaultConnection");

app.Run();

Certo, em seguida, você só precisa criar mais duas classes para realizar o processo de configuração do hub. A primeira classe, chamada “ChatHubNotification”, tem a responsabilidade de se comunicar com o cliente, implementando o Hub da biblioteca SignalR. A segunda classe, chamada “ListenDatabase”, tem a responsabilidade de escutar o banco com o SqlDependency e enviar a informação para o cliente.

using Microsoft.AspNetCore.SignalR;

namespace WebApplication1
{
public class ChatHubNotification : Hub
{
public void Start()
{
ChatHub chatHub = new ListenDatabase(this.Clients);
chatHub.ConsultarTabelaEmTempoReal();
}
}
}

No exemplo acima, temos o método “Start” que será utilizado para iniciar a escuta da tabela do banco de dados. Ele serve como ponto de partida para dar início ao processo de escuta.

using Dapper;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Data.SqlClient;


namespace WebApplication1
{
public sealed class ListenDatabase
{
private string _connectionString;
private IHubCallerClients _hubContext { get; }

public ListenDatabase(IHubCallerClients clients)
{
//Recebendo os dados de client do Hub
_hubContext = clients;
_connectionString = "Dados de conexão do seu banco";

//Iniciando o SqlDependecy
SqlDependency.Start(_connectionString);
}

public void ConsultarTabelaEmTempoReal()
{
//Consulta Sql que representa a tabela que vai ficar sendo escultada
string consultaDeEscutar = "SELECT [Mensagens] FROM [dbo].[Mensagem]";

using (SqlConnection connection = new(_connectionString))
{
using (SqlCommand command = new(consultaDeEscutar, connection))
{
connection.Open();

// Configurando o SqlCommand para notificação
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);

// Criando o manipulador de eventos para a notificação
dependency.OnChange += new OnChangeEventHandler(SqlDependency_OnChange);
command.ExecuteReader();
}
connection.Close();
}
}

private void SqlDependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
using (SqlConnection connection = new SqlConnection(_connectionString))
{
var msg = connection.Query<string>("SELECT TOP 1 mensagens FROM Mensagem ORDER BY id DESC");
// Notificando o client
_hubContext.All.SendAsync("Send", msg);
}
}
//Solicitando para escultar novamente o banco com sua tabela
ConsultarTabelaEmTempoReal();
}
}
}

No exemplo acima, temos um método chamado “ConsultarTabelaEmTempoReal” que consulta os dados de uma tabela do banco e cria uma dependência com o SQL Server para monitorar a tabela configurada. Após isso, todo o processo de comunicação é realizado através do SqlCommand, que possui propriedades específicas para escutar o banco a cada atualização. Em seguida, é passado um OnChangeEventHandler que irá chamar um método após uma alteração na tabela, seja ela um Insert, Delete ou Update.

Através do SqlCommand e das propriedades específicas para monitorar o banco, garantimos que o método definido no OnChangeEventHandler seja acionado sempre que ocorrer uma alteração na tabela. Dessa forma, podemos utilizar essas informações para atualizar outras partes do sistema, notificar usuários ou realizar qualquer outra ação desejada. Neste exemplo, estou realizando uma consulta que retorna para o cliente a última mensagem. Contudo, você pode informar qualquer informação da tabela, até mesmo mais dados. Para enviar mais informações, é necessário criar o DTO e enviar um objeto para o cliente com uso do Dapper, por exemplo:

var msg = connection.QueryAsync<ObjetoDto>("SELECT Coluna1, Coluna2, Coluna3 ... FROM Tabela");

O método “hubContext.All.SendAsync(“Send”, msg)” é parte da biblioteca do SignalR. Através desse método, definimos o tipo de informação que será transmitida entre o servidor e o cliente. Nesse exemplo específico, estou usando o nome “Send” para indicar como a mensagem será transmitida. Essa mesma informação “Send” também deve ser utilizada no lado do Angular para receber a mensagem.

Essa funcionalidade proporciona um maior controle e automação nos processos de uma aplicação, permitindo uma comunicação eficiente e em tempo real com o banco de dados, como se fosse uma trigger que acionada sempre houver alguma alteração. Por fim não esqueça de chamar novamente o método “ConsultarTabelaEmTempoReal” para voltar a escuta a tabela.

Agora vamos testar a comunicação do WebSocket usando o Postman. Para isso, basta executar a aplicação e depois configurar o WebSocket no Postman. Selecione um novo processo e escolha o WebSocket.

Depois basta escolher o url da sua Api e substituir o https para wss ou ws para http.

Após definir a URL, é necessário definir a mensagem inicial que você sempre deve enviar ao hub e especificar o protocolo. Neste caso, vamos nos comunicar usando JSON. No final os caracteres ASCII character 0x1E devem ser informados na mensagem.

Copie todo JSON abaixo. No Postam deve conter os caracteres especiais no final, conforme a imagem acima.

{"protocol":"json","version":1}

Após isso, é necessário iniciar o processo do SignalR. Para fazer isso, basta invocar o método Start da classe “ChatHubNotification”. Como esse método não possui argumentos, o parâmetro “arguments” pode ser deixado em branco. O invocationId pode ser definido como 0 para indicar um novo servidor. No parâmetro “target”, deve ser informado o nome do método que será chamado e, no parâmetro “type”, deve ser definido o tipo de retorno.

Copie todo o JSON abaixo, até mesmo o caractere especial no final, conforme a imagem acima.

{"arguments":[],"invocationId":"0","target":"Start","type":1}

O processo foi iniciado, basta incluir um dado no banco e ele aparece automaticamente no Postman.

Acima o resultado do Postaman com Sql Serve.

Agora chegamos na última etapa do projeto: faremos com que o cliente em Angular possa analisar todas as alterações na tabela que parametrizamos usando SignalR. Com essa informação, conseguiremos realizar uma comunicação entre cliente e servidor de forma dinâmica e em tempo real.

Primeiro, devemos baixar o biblioteca do SignalR no projeto angular.

npm install @microsoft/signalr
# or
yarn add @microsoft/signalr

Em seguida, devemos configura o Service do Angular para poder receber a escuta do Signal.

import { Injectable } from '@angular/core';
import * as signalR from "@microsoft/signalr";
import { Subject } from 'rxjs';
import { environment } from 'src/environments/environment';

@Injectable({
providedIn: 'root'
})
export class SignalRServiceService {
private hubConnection: signalR.HubConnection;
public receivedData: Subject<unknown> = new Subject<unknown>();

public startConnection() {
//Definar sua rota do hub, no meu caso seria o chat-hub
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl('wss://localhost:7166/chat-hub', {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
})
.build();

if(this.hubConnection.state == signalR.HubConnectionState.Connected){
this.hubConnection.stop();
}

this.hubConnection
.start()
.then(() => this.addDataListener())
.catch(err => console.log('Erro ao conectar com o hub', err));
}

public addDataListener() {
//Chamar o método Start que contém a inicialização do .Net
this.hubConnection.invoke('Start');

//O nome Send é o mesmo que foi enviado no Serve chamado
//_hubContext.All.SendAsync("Send", msg); ele serve para ter a resposta
//do serve para Client
this.hubConnection.on('Send', (data: unknown) => {
console.log('Recebendo dados do hub', data);
this.receivedData.next(data);
});
}

public stopConnection() {
this.hubConnection.stop();
}
}

Agora vamos ter que chamar o Service no componente, para poder repassar a informação para cliente.

import { Component, HostListener, OnDestroy, OnInit } from '@angular/core’;
import { SignalRServiceService } from 'src/app/core/services’;

@Component({
selector: 'app-mensagem’,
templateUrl: './mensagem.component.html’,
styleUrls: [’./mensagem.component.scss’],
})
export class MensagemComponent implements OnInit {
notificacaoHub = "";

constructor(private signalRService: SignalRServiceService) { }
ngOnInit(): void {
this.startHub();
}

startHub() {
this.signalRService.startConnection();
this.signalRService.receivedData.subscribe((mensagem: any) => {
this.notificacaoHub= mensagem;
});
}
}

Para repassar para o cliente, no seu HTML, você deve passar a variável através de interpolação que foi definida no seu arquivo .ts do seu componente.

<div>
{{notificacaoHub}}
</div>

Por fim, a aplicação ficaria assim: a cada alteração no banco de dados, a mensagem seria alterada no Angular, criando uma aplicação em tempo real que capturaria cada ação do banco, seja Insert, Delete ou Update.

Em resumo, a criação de uma aplicação em tempo real é uma maneira eficaz de fornecer informações instantâneas e atualizadas aos usuários. Ao utilizar a tecnologia adequada, como banco de dados NoSQL, é possível realizar esse processo de forma mais rápida e objetiva. Embora esse exemplo utilize o SGBD SQL Server, também é possível utilizar outros bancos relacionais como MySQL, Oracle ou PostgreSQL. A única diferença é a forma de habilitar a escuta de banco de dados, já que cada SGBD possui suas bibliotecas nativas ou de terceiros para essa finalidade. É importante considerar as necessidade de utilizar um banco de dados relacional para operações em tempo real, avaliando os custos e benefícios. Os bancos NoSQL, como MongoDB ou Firebase, podem oferecer uma solução mais rápida e com melhor custo-benefício nesta situação.

Por fim, é isso. Espero ter ajudado de alguma forma.

--

--

Yuri Nabaia
TOTVS Developers

Sou analista de desenvolvimento na TOTVS, estou compartilhando conhecimento adquiridos nos meus anos como desenvolvedor.