Azure SignalR no .NET 8

hgmauri

Henrique Mauri

Posted on December 13, 2023

Azure SignalR no .NET 8

SignalR

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:

image

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:

image

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.

image

Logo depois da instalação, é necessário copiar a connection string, que será utilizada na nossa codificação .NET:

image

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.

image

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();
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

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}");
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

image

Ao executar os três projetos, é possível ver a orquestração de mensagens do SignalR:

image

É 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

💖 💪 🙅 🚩
hgmauri
Henrique Mauri

Posted on December 13, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Azure SignalR no .NET 8
dotnet Azure SignalR no .NET 8

December 13, 2023