SIngalR-logo

Há algum tempo atrás eu falei sobre comunicação em tempo real com Xamarin Forms e Firebase Real time Database.

Neste post vamos ver uma alternativa ao Firebase Real time Database para a comunicação em tempo real em aplicativos mobile, o ASP.NET Core SignalR da Microsoft.

O SignalR é uma biblioteca já conhecida por muitas pessoas desenvolvedoras .Net na comunicação em tempo real entre sistemas web.

A última versão do SignalR (ASP.Net Core SignalR) é compátivel com .Net Standart 2.0, isso significa que podemos utilizá-lo no Xamarin!

ASP.NET Core SignalR

De acordo com a documentação oficial do produto o ASP.NET Core SignalR é:

SignalR do ASP.NET Core é uma biblioteca de software livre que simplifica a adição da funcionalidade da web em tempo real aos aplicativos. A funcionalidade da web em tempo real permite que o código do lado do servidor para conteúdo de envio por push aos clientes instantaneamente.

Estrutura do SignalR

De forma simplista, o funcionamento do SignalR se dá a partir de um Hub e um ou mais Clients. O hub de mensagens responsável por receber e distribuir as mensagens a todos os clients conectados de forma simultânea. Veja a ilustração abaixo:

signalr-diagram

Existem muitas configurações que você pode fazer no seu ASP.NET Core SignalR como configurações de autorização, autenticação entre outros. Este post irá focar na integração entre Xamarin Forms e ASP.Net Core SignalR. Você pode ver configurações adicionais clicando aqui.

Criando meu Hub de mensagens

Vamos criar nosso próprio Hub de mensagens que irá receber uma mensagem e enviá-la para os clientes conectados.

Para isto, selecione File > New Project e selecione ASP.NET Core Web Application e selecione o template API.

A intenção do exemplo é trafegar os dados de um pedido de um app para outro em tempo real. Para tal, vamos criar nossa classe de modelo do Pedido.

Crie uma nova pasta chamada de Models e com o botão direito do mouse encima dela selecione Add > New Item para criar a nossa classe Pedido:

public class Pedido
{
    public string Nome { get; set; }
    public string Produto { get; set; }
    public decimal Valor { get; set; }
}

Agora precisamos criar nosso Hub de pedidos.
Vamos criar uma nova pasta na solution chamada Hubs e dentro dela uma nova classe chamada PedidosHub.

O sufixo Hub é apenas para que seja fácil de identificar que se trata de um Hub do ASP.NET Core SignalR, mas não é obrigatório.

A classe PedidosHub irá herdar de Hub da namespace Microsoft.AspNetCore.SignalR.

Então vamos criar um novo método que irá enviar para os clientes conectados através do objeto Clients, herdado da classe Hub. Este método deve receber como parâmetro qual o usuário cliente que é destinado a mensagem e qual o objeto a ser enviado. O método SendAsync irá enviar os dados para todos os clientes conectados a ReceberPedido. Veja abaixo:

public class PedidosHub : Hub
{
    public async Task EnviarPedido(string user, Pedido pedido)
    {
        await Clients.All.SendAsync("ReceberPedido", user, pedido);
        
    }
}

Vamos precisar fazer algumas modificações também na classe Startup.cs. No método ConfigureServices adicione o SignalR com a linha abaixo:


services.AddSignalR();

No método Configure, abaixo do middleware do Mvc, vamos adicionar o middleware do SignalR informando a rota para nosso Hub de pedidos:

app.UseSignalR(routes =>
{
    routes.MapHub<PedidosHub>("/pedidosHub");
});

Se você estiver desenvolvendo localmente comente a linha do middleware UseHttpsRedirection() para garantir que os apps conseguirão acessar seu localhost. Quando for publicar, lembre-se de reativa-lo.

Enviando e Recebendo mensagens em Tempo Real

A transmissão das mensagens será realizada pelo Hub que criamos acima, mas para que isto aconteça é preciso alguém que envie as mensagens para o Hub e alguém que assine o subscriber do Hub para recebe-las em tempo real. Neste exemplo, para não nos alongarmos muito, vamos criar um app Xamarin Forms que irá enviar a mensagem para o Hub e também assinar o ReceberPedidos para recebermos a mensagem em tempo real. Para ter certeza de que tudo está funcionando, vamos receber as mensagens e adiciona-las a uma listview apenas se o usuário que enviou a mensagem for Test. Para isto, abra uma nova instancia do Visual Studio e crie um novo projeto Xamarin Forms.

No nosso cliente, também vamos precisar criar o nosso modelo de Pedido que será trafegado pelo Hub. Então vamos criar uma nova pasta chamada de Models e criar nela nossa classe Pedido:


public class Pedido
{
    public string Nome { get; set; }
    public string Produto { get; set; }
    public decimal Valor { get; set; }
}

Agora que já temos o modelo, vamos criar nossa view que irá preencher os dados do modelo por binding através de uma ViewModel, que construiremos posteriormente. Na MainPage.xaml, vamos adicionar o código abaixo:

<StackLayout Margin="20">
    <Label Text="Nome" />
    <Entry Placeholder="Nome do cliente" Text="{Binding Pedido.Nome}" />

    <Label Text="Produto" />
    <Entry Placeholder="Produto" Text="{Binding Pedido.Produto}" />


    <Label Text="Valor" />
    <Entry Placeholder="Valor do pedido" Text="{Binding Pedido.Valor}" />

    <Button Command="{Binding EnviarPedidoCommand}" Text="Enviar Pedido" VerticalOptions="FillAndExpand" />


    <ListView Margin="0,10,0,0" ItemsSource="{Binding Pedidos}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout>
                        <Label Text="{Binding Nome}" />
                        <Label Text="{Binding Produto}" />
                        <Label Text="{Binding Valor}" />
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackLayout>

Repare que criamos os Entrys para preenchimento do modelo, um command para envio do pedido para o Hub e também uma ListView que irá exibir os pedidos realizados por Test que foram disparados pelo Hub.

Para a ViewModel, vamos adicionar uma nova pasta na solution chamada ViewModels e adicionar uma classe chamada de MainPageViewModel.cs.

Na MainPageViewModel vamos ter uma propriedade do tipo Pedido e uma ObservableRangeCollection de Pedidos que irá conter os pedidos realizados por Test, além do command que irá enviar os pedidos para o Hub:

public class MainPageViewModel : BaseViewModel
{
    private Pedido _pedido;

    public Pedido Pedido
    {
        get { return _pedido; }
        set { _pedido = value; OnPropertyChanged(); }
    }

    private ObservableRangeCollection<Pedido> _pedidos;

    public ObservableRangeCollection<Pedido> Pedidos
    {
        get { return _pedidos; }
        set { _pedidos = value; OnPropertyChanged(); }
    }

    public ICommand EnviarPedidoCommand { get; set; }



    public MainPageViewModel()
    {
        Pedidos = new ObservableRangeCollection<Pedido>();
        Pedido = new Pedido();

        EnviarPedidoCommand = new Command(async () =>
        {
            await EnviarPedido(Pedido.Nome, Pedido);
        });
    }

    
    public async Task EnviarPedido(string user, Pedido pedido)
    {
        
    }
}

Para este post estou utilizando o pacote MVVMHelpers do James Montemagno para facilitar a criação da ViewModel.

Não se esqueça de setar o Binding Context da página para a MainPageViewModel

Legal, agora já temos um model, uma ViewModel e uma View e para preencher os dados do pedido. Até aqui nenhuma novidade no Xamarin.

Criando a conexão com o Hub do ASP.NET Core SignalR

O primeiro passo é adicionar o pacote Nuget do ASP.NET Core SignalR no projeto. No gerenciador de pacotes da solution, procure pelo package Microsoft.ASpNetCore.SignalR.Client e instale o pacote em todas as plataformas que o projeto for ser executado, além do .Net Standart.

Voltando para nossa MainPageViewModel, vamos importar o namespace do ASP.NET Core SignalR para utilizarmos nesta classe:

using Microsoft.AspNetCore.SignalR.Client;

Depois de importar o namespace, vamos precisar de uma instancia do objeto HubConnection. Toda a iteração com o Hub do ASP.NET Core SignalR será realizada através deste objeto. Vamos criar o objeto e instancia-lo no construtor da classe:

HubConnection hubConnection;

    public MainPageViewModel()
    {
        Pedidos = new ObservableRangeCollection<Pedido>();
        Pedido = new Pedido();

        var ip = "localhost";

        if (Device.RuntimePlatform == Device.Android)
            ip = "10.0.2.2";

        hubConnection = new HubConnectionBuilder()
            .WithUrl($"http://{ip}:5000/pedidosHub")
            .Build();

            
            EnviarPedidoCommand = new Command(async () =>
            {
                await EnviarPedido(Pedido.Nome, Pedido);
            });

    }

A um pequeno detalhe aqui que vale a pena ser citado. Observe que estamos verificando se a plataforma que o app está sendo executada é Android, ser for, trocaremos o ip de localhost para 10.0.2.2. Esta é um característica do emulador Android para endereços locais. Se o seu Hub já estivesse publicado em algum servidor como Azure, não seria necessária esta distinção, você apenas apontaria o ip para o endereço que o Hub está hospedado.

Recebendo mensagens em tempo real

Até o momento estamos estabelecendo a conexão com o Hub do ASP.NET Core SignalR, mas ainda não especificamos quais mensagens queremos escutar nesta conexão, faremos isto agora, ainda no construtor da classe, logo após o código inserido acima:

hubConnection.On<string, Pedido>("ReceberPedido", (user, pedido) =>
{
    if(user == "Test")
        Pedidos.Add(pedido);
});

Observe que estamos verificando se o user que enviou a mensagem é o Test, como falamos que seria a regra. Você pode adicionar a regra que melhor se adaptar a resolução do seu problema.
O método On do objeto hubConnection pede que informemos dois tipos, o primeiro tipo será referente a um parâmetro enviado para o Hub(no caso estamos tratando como nome do usuário) e o segundo parâmetro é o tipo do objeto trafegado, no nosso exemplo será o tipo Pedido.

Os parâmetros do método On são o nome do subscriber que queremos escutar e uma função a ser executada quando recebermos uma nova mensagem.

Vamos escutar as mensagens de ReceberPedido. Existe uma ligação entre a string que informamos aqui e o nome do subscriber que demos lá no Hub na classe PedidosHub. Estamos dizendo para o app escutar as mensagens de ReceberPedidos que será disparado pelo Hub. Logo ReceberPedidos precisa existir e ser disparado pelo Hub.

Agora que já assinamos ReceberPedidos, vamos enviar a mensagem para o Hub a partir do app!

Enviando mensagens em tempo real

No método EnviarPedido, vamos adicionar o código abaixo:

public async Task EnviarPedido(string user, Pedido pedido)
{
    try
    {
        await hubConnection.InvokeAsync("EnviarPedido", user, pedido);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

Veja que, diferentemente do método On que estamos escutando ReceberPedido, para enviar a mensagem para o Hub estamos utilizando o método InvokeAsync do hubConnection informando qual é o método existente no PedidosHub que irá receber esta mensagem e então executar alguma lógica e/ou disparar a mensagem para todos os clientes conectados. Por isto, estamos informando para o o método InvokeAsync EnviarPedido no primeiro parâmetro, porque este é o nome do método que criamos na classe PedidosHub. O segundo parâmetro será um identificador e o segundo o objeto a ser trafegado.

Pronto! Com este código nós já estamos prontos para enviar e receber mensagens em tempo real utilizando o ASP.NET Core SignalR!

Boas práticas

Para um gerenciamento melhor de recursos do smartphone e que nossa app não fique utilizando recursos de forma desnecessária, lembre-se sempre de conectar e desconectar o app do Hub sempre que o usuário sair da página que está conectada ao Hub ou quando o app for finalizado.
Para isto, vamos adicionar mais dois métodos na MainPageViewModel:

public async Task Connect()
{
    try
    {
        await hubConnection.StartAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

public async Task Disconnect()
{
    try
    {
        await hubConnection.StopAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex); ;
    }
    
}

Você pode adotar a melhor estratégia para utilizar estes métodos, uma forma simplista para verificar se tudo está funcionando corretamente em ambiente de desenvolvimento é adicionar nos métodos OnAppearing e OnDisappearing da Page.

Mas e se o app estiver offline?

O ASP.NET Core SignalR por si só não irá fazer um gerenciamento de envio / recebimento de mensagens em caso do usuário estar offline. Portanto cabe a pessoa desenvolvedora definir a melhor estratégia para tratar este cenário.

Conclusão

O ASP.NET Core SignalR é uma ótima opção para o envio e recebimento de mensagens em tempo real. Ele proporciona para a pessoa desenvolvedora um maior controle do que está acontecendo do que ferramentas PaaS que oferecem este tipo de serviço. Ele é um middleware do ASP.NET Core então, a configuração básica dele é bem simples, e é possível personalizar com a regra de negócio que melhor se adeque a sua necessidade.

O código fonte deste exemplo está disponível no meu GitHub.

Referências:

Neste post utilizei as referências:

https://docs.microsoft.com/pt-br/aspnet/core/signalr/introduction?view=aspnetcore-2.2

Real Time Communication for Mobile with SignalR (Core) – James Montemagno

#Ubuntu

(Cross-post de http://rsamorim.azurewebsites.net/2019/01/08/aspnet-core-signalr-enviando-e-recebendo-mensagens-em-tempo-real-com-xamarin/)

Robson Soares Amorim