DEV Community

Henrique Mauri
Henrique Mauri

Posted on

Azure SignalR no .NET 5.0

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

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 5.0

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

Discussion (0)