Azure SignalR no .NET 8
Tags: dotnet, dotnetcore, signalr, socket
Published: 2023-12-13 18:42:16

![SignalR](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4m1spjal7s1nasa6kz0q.png)

O [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) é uma biblioteca *open source* da Microsoft criada em 2011 por [Damian Edwards](https://twitter.com/damianedwards) e [David Fowler](https://twitter.com/davidfowl), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9e8rz3ig3uqnyph7ddi.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pjxaogxoyhtsuay365g6.png)

Utilizaremos o [tipo de serviço](https://docs.microsoft.com/pt-br/azure/azure-signalr/concept-service-mode) **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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/notxobgp2ysq644k7k5j.png)

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8bzjk9n3u5qyg9syt3k.png)

**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9s4cp2fuzrhrl07p7fqo.png)

No projeto **Server** é necessário instalar o pacote [Microsoft.Azure.SignalR](https://www.nuget.org/packages/Microsoft.Azure.SignalR/) e configurar nosso *startup.cs* com a *connection string* do Azure SignalR

```csharp
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](https://docs.microsoft.com/pt-br/aspnet/core/signalr/configuration?view=aspnetcore-5.0&tabs=dotnet), onde também indicam as boas práticas de utilização.

Nosso próximo passo é configurar o *hub*, chamado de *SocketHub.cs*:

```csharp
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](https://www.nuget.org/packages/Microsoft.AspNetCore.SignalR.Client) no projeto **Sender** (*Console Application*), responsável por enviar mensagens:

```csharp
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**:

```csharp
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:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8oeukluwioazq9av769l.png)

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

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n60rk73rlx25nnnmn9z4.png)

É 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](https://github.com/hgmauri/signalr-socket-dotnet5)