Azure SignalR no .NET 8
Henrique Mauri
Posted on December 13, 2023
O SignalR é uma biblioteca open source da Microsoft criada em 2011 por Damian Edwards e David Fowler, com a finalidade de facilitar a implementação de aplicações que demandam funcionalidades em tempo real. Essa funcionalidade permite que o código do lado do servidor envie conteúdo aos clientes instantaneamente.
Um exemplo clássico de utilização do SignalR é o chat, onde um cliente envia mensagem para outro cliente em tempo real, passando pelo servidor SignalR, que faz o papel de hub entre as duas conexões, trabalhando como uma classe que fornece endpoints que tornam o envio e o recebimento de mensagens em tempo real possível.
Neste artigo utilizaremos o Azure SignalR como serviço em um projeto .NET 8.
Configurando o SignalR no Azure
Os maiores benefícios em utilizar o SignalR no Azure são: escalabilidade, balanceamento de carga e baixa latência, deixando seu ambiente muito mais transparente, robusto e confiável.
Nosso primeiro passo é preparar e configurar o SignalR no Azure, clicando no serviço informado conforme imagem abaixo:
Em seguida, é necessário configurar os dados básicos da instalação. Neste exemplo utilizaremos a região sul do Brasil para instalar nosso servidor de socket:
Utilizaremos o tipo de serviço Default com o plano Standard do SignalR, esse plano é ideal para produção, pelo principal fato de contar com SLA de 99.9%. Em média, o valor mensal para esse serviço gira em torno de R$ 250,00, o que é muito barato, por exemplo, para uma infraestrutura com várias aplicações.
Logo depois da instalação, é necessário copiar a connection string, que será utilizada na nossa codificação .NET:
Configurando o SignalR no .NET 8
Para entender melhor como o SignalR funciona, utilizaremos três projetos:
- SignalR.Socket.Server: Projeto principal utilizado como servidor socket, nele fica configurado o Azure SignalR e nossos hubs.
- **SignalR.Socket.Receive: Projeto responsável por receber notificações que passam pelo SignalR.
- SignalR.Socket.Sender: Projeto de exemplo de envio de mensagens de uma ponta à outra.
No projeto Server é necessário instalar o pacote Microsoft.Azure.SignalR e configurar nosso startup.cs com a connection string do Azure SignalR
public class Startup
{
const string CORS_SIGNALR_POLICY_NAME = "signalr";
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(options =>
{
options.KeepAliveInterval = TimeSpan.FromMinutes(20);
options.ClientTimeoutInterval = TimeSpan.FromMinutes(40);
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
options.MaximumParallelInvocationsPerClient = 10;
options.MaximumReceiveMessageSize = 10 * 1024 * 1024;
options.StreamBufferCapacity = 50;
options.EnableDetailedErrors = true;
}).AddAzureSignalR(Configuration.GetSection("AzureSignalR:ConnectionString").Value);
services.AddCors(options =>
{
options.AddPolicy(CORS_SIGNALR_POLICY_NAME, builder => builder
.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed((host) => true)
.AllowCredentials()
);
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseCors(CORS_SIGNALR_POLICY_NAME);
app.UseRouting();
app.UseAuthorization();
app.UseAzureSignalR(routes =>
{
routes.MapHub<SocketHub>("/sockethub");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Socket no ar!");
});
endpoints.MapControllers();
});
}
}
Repare que a configuração do CORS está permitindo todos os tipos de conexões, o que não é indicado por questão de segurança, deixado apenas para realizar nossos testes.
Existem configurações extras em AddSignalR, que foram de acordo com a documentação oficial, onde também indicam as boas práticas de utilização.
Nosso próximo passo é configurar o hub, chamado de SocketHub.cs:
public class SocketHub : Hub
{
public override async Task OnConnectedAsync()
{
var context = Context.GetHttpContext();
lock (UserSocket.UsersSocket)
{
UserSocket.UsersSocket.Add(new Users
{
DateTime = DateTime.Now,
Application = context?.Request?.Headers["Host"],
Environment = context?.Request?.Headers["Origin"],
ConnectionId = Context.ConnectionId,
UserName = Context.User?.Identity?.Name ?? Context.ConnectionId
});
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
var user = UserSocket.UsersSocket?.FirstOrDefault(p => p.ConnectionId == Context?.ConnectionId);
if (user != null)
{
lock (UserSocket.UsersSocket)
{
UserSocket.UsersSocket.Remove(user);
}
}
await base.OnDisconnectedAsync(exception);
}
public async Task SendPrivateMessage(string login, string type, string message, string body)
{
var connectionId = UserSocket.UsersSocket.Where(x => x.UserName == login);
foreach (var connection in connectionId)
{
await Clients.Client(connection.ConnectionId).SendAsync("ReceiveMessage", login, type, message, body);
}
}
public async Task SendNotification(string mensagem)
{
await Clients.All.SendAsync("ReceiveGenericEvent", mensagem, DateTime.Now.Ticks.ToString());
}
}
Além dos métodos de sobrecarga de conectar e desconectar, temos o método SendPrivateMessage responsável por enviar uma mensagem para um usuário específico e o método SendNotification, que é responsável por enviar uma mensagem para todos clientes que estiverem "escutando" o método ReceiveGenericEvent.
amos configurar o pacote Microsoft.AspNetCore.SignalR.Client no projeto Sender (Console Application), responsável por enviar mensagens:
static async Task SenderClient(string nomeUsuario)
{
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5005/sockethub", options =>
{
options.Headers["Application"] = "API Sender";
})
.WithAutomaticReconnect()
.Build();
await connection.StartAsync();
Console.WriteLine("Connection started.");
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
while (true)
{
Thread.Sleep(400);
await connection.SendAsync($"SendNotification", $"{nomeUsuario} - {DateTime.Now:G}");
Console.WriteLine($"Send Message: {nomeUsuario} - {DateTime.Now:G}");
}
}
A classe HubConnectionBuilder é responsável pela conexão com nosso servidor SignalR (que está em execução na url http://localhost:5005/sockethub), nela também temos o método WithAutomaticReconnect() que é utilizado como resiliência para manter a conexão sempre ativa com nosso servidor.
Finalmente temos a configuração do projeto Receive (Console Application), responsável por receber todas as mensagens enviadas pelo projeto Sender:
static async Task Main(string[] args)
{
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5005/sockethub", options =>
{
options.Headers["Application"] = "API Receive";
})
.WithAutomaticReconnect()
.Build();
await connection.StartAsync();
Console.WriteLine("Connection started.");
connection.On<string, string>("ReceiveGenericEvent", async (id, runningTime) =>
{
await ReceiveAsync(id, runningTime);
});
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
Console.ReadLine();
}
private static Task ReceiveAsync(string id, string runningTime)
{
Console.WriteLine($"Receive Message: {id} - {runningTime}");
return Task.CompletedTask;
}
O subscribe do método ReceiveGenericEvent do código acima é configurado no método SendNotification do nosso SocketHub.cs.
Para testar, é necessário marcar todos os três projetos como inicializáveis no Visual Studio:
Ao executar os três projetos, é possível ver a orquestração de mensagens do SignalR:
É muito simples, seguro e prático de configurar e utilizar o Azure SignalR, uma ferramenta incrível que pode ser utilizado em vários cenários, principalmente em ambientes com microservices, comunicações entre o backend / frontend e chamadas assíncronas.
Os detalhes completos você encontra no meu GitHub: https://github.com/hgmauri/signalr-socket-dotnet5
Posted on December 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.