Maio de 2018

Volume 33, Número 5

Cutting Edge - Dentro do ASP.NET Core SignalR

Por Dino Esposito

Dino EspositoSignalR é a adição mais recente para a plataforma principal do ASP.NET e uma longa esperado nesse. Dare digo que agora apenas com SignalR na placa pode realmente começamos examinando ASP.NET Core como pronta para todos os tipos de aplicativos. Nenhum aplicativo Web grave hoje pode fazer sem alguma forma de notificação assíncrona e recursos em tempo real.

O SignalR que saber do ASP.NET foi totalmente reescrito e agora reside na família de bibliotecas do ASP.NET Core. Na minha coluna último mês (msdn.com/magazine/mt846469), oferecido um rápido tour dos recursos e funcionalidades do ASP.NET Core SignalR novo. Na coluna, discutiremos o mecanismo interno.

SignalR é uma camada de abstração para bidirecional, chamadas de procedimento remoto bidirecional (RPC) e funciona em uma variedade de protocolos de transporte. É independente de host e não se limitando a HTTP. A versão mais recente, ele pode transferir dados binários e não apenas JSON com base em mensagens. Ao configurar o SignalR para ASP.NET ou ASP.NET Core, você pode selecionar o protocolo de transporte e o protocolo de mensagem. Se você não fizer uma escolha explícita, o protocolo de transporte será escolhido automaticamente e na maioria das vezes ele se torna o WebSocket. O protocolo de mensagem, por outro lado, é baseado em JSON. Vamos examinar o que acontece quando um cliente — por exemplo, um cliente Web — configura uma conexão com um ponto de extremidade do servidor. O código a seguir inicia uma conexão.

var progressConnection = new signalR.HubConnection("/progressDemo");
progressConnection.start();

Se você monitorar o tráfego da Web gerado por este código com uma ferramenta como o Fiddler, você verá que as duas solicitações HTTP são enviadas. Figura 1 mostra os detalhes da primeira solicitação que inicializa a conversa.

Detalhes da solicitação inicial de início
Figura 1 detalhes da solicitação inicial de início

A solicitação inicial é um HTTP POST destinados a rota SignalR especificada na classe de inicialização, seguido por uma / negociar segmento. O código a seguir mostra como definir uma rota SignalR:

app.UseSignalR(routes =>
{
  routes.MapHub<ProgressHub>("/progressDemo");
});

Com base na rota, a chamada inicial será direcionada a URL: progressdemo/negociar.

Observe que em versões de visualização do SignalR o verbo de opções foi usado para a mesma finalidade. Servidor de ponto de extremidade SignalR retorna um objeto JSON configurado da seguinte maneira:

{
  "connectionId" : "b6668ac0-1083-499f-870a-2a5376bf5047",
  "availableTransports" : [
    "WebSockets", "ServerSentEvents", "LongPolling"
  ]
}

Como você pode ver, a resposta JSON contém duas coisas: a ID exclusiva da conexão estabelecida apenas e uma lista de protocolos de transporte disponíveis para uso. O código de exemplo indica que WebSockets, ServerSentEvents e LongPolling podem ser usados considerando a configuração de cliente e servidor. O que acontece em seguida depende de transporte na verdade escolhido pelo cliente.

SignalR tende a usar o WebSocket se possível. Se não, ele verifica ServerSentEvents e depois que reverterá para LongPolling. Se o WebSocket pode ser usado, uma solicitação HTTP GET é colocada para a mesma URL como antes. Neste momento a solicitação também adiciona a ID de conexão como um parâmetro de cadeia de caracteres de consulta. A solicitação GET é na verdade uma solicitação de atualização de protocolo. Mais precisamente, uma atualização do protocolo é uma normal solicitação HTTP (GET, mas ele também pode ser uma POSTAGEM) com dois cabeçalhos ad-hoc. Um é o cabeçalho de Conexão, que deve ser definido para a atualização. A outra é a atualização, que deve ser definida como o nome do protocolo necessário — nesse caso, o WebSocket. Se a atualização de protocolo for aceita, o servidor retornará uma resposta HTTP 101 alternar protocolos (conforme mostrado na Figura 1).

Protocolos de transporte

SignalR pode empregar diversos protocolos de transporte. O cliente pode forçar a conexão para ocorrer em um protocolo específico, mas por padrão o protocolo é determinado automaticamente. Para solicitar um transporte específico, basta adicione um parâmetro extra para o código JavaScript (ou código de cliente, se um cliente Web não for usado). Veja um exemplo:

var progressConnection = new signalR.HubConnection(
  "/progressDemo",
  {transport : signalR.TransportType.WebSocket});

Observe que, ao passar uma matriz em vez de um nome direto, você pode restringir a escolha para um dos protocolos especificados alguns. Obviamente, protocolos de transporte diferem em alguns aspectos. Figura 2 lista as principais características de cada protocolo.

Figura 2 suporte para protocolos de transporte

Protocolo Descrição
WebSockets Com base em uma conexão de um para um entre o cliente e servidor. Cliente e servidor gravam em um pipe de compartilhado para que o fluxo de dados é bidirecional. O protocolo não pode ser usado em qualquer lugar e é limitado pelo navegador e o servidor que está sendo usado para a conexão. No cliente, é necessário um navegador moderno. As versões mais recentes de todos os navegadores comuns funcionam normalmente, com exceção do Internet Explorer anteriores à versão 10. No lado do servidor, ao usar o IIS ou HttpSysServer, Windows 8 ou posterior é necessário no cliente.
ServerSentEvents Com base no objeto de EventSource ativo no servidor. O objeto que representa um pipe de conexão do servidor para o cliente. Quando a conexão é estabelecida, o servidor pode enviar eventos continuamente enquanto o cliente pode se comunicar por meio de chamadas AJAX simples. O protocolo de datas de volta para os dias iniciais da Netscape e não tem suporte nos navegadores Internet Explorer ou borda.
LongPolling O protocolo funciona através da abertura de uma conexão com o servidor a ser usado para futuras respostas. A conexão permanece pendente até que uma resposta será enviada ou a solicitação expire. Em qualquer caso, quando a conexão é fechada, o cliente imediatamente restabelece-lo para que a sondagem é contínua, mas o tráfego é limitado ao que é estritamente necessário. Esse protocolo funciona com todas as versões de todos os navegadores e é considerado uma solução de fallback.

Se a comunicação entre o cliente da Web e o servidor estiver entre domínios, o servidor deve ser habilitado CORS. Nesse caso, você pode usar qualquer protocolo disponível (Observe que JSONP não tem suporte no SignalR do ASP.NET Core):

public void ConfigureServices(IServiceCollection services)
{
  services.AddCors();
}

O navegador adiciona o cabeçalho de origem para as chamadas AJAX e WebSockets do ASP.NET Core SignalR. Além disso, você precisa ter o middleware CORS configurado em seu aplicativo SignalR para garantir que o navegador permite que as solicitações.

Protocolos de mensagens

SignalR no ASP.NET sempre serializado trocadas mensagens usando o formato baseado em texto JSON. O mais recente do ASP.NET Core SignalR adiciona um protocolo de mensagem binária baseado no formato de MessagePack. Esse formato de serialização binária permite a troca de dados de forma independente da linguagem, muito do mesmo modo JSON.

MessagePack é mais compacto e mais rápido para transferir de JSON e tamanhos de pacote de recursos que são ainda mais compacta que Binary JSON (BSON) – o tipo de otimização MongoDB do JSON. Com MessagePack, inteiros pequenos e valores nulos consomem apenas um byte de dados. Por comparação, valores nulos de JSON consome 4 bytes. Para obter mais informações sobre o protocolo, check-out msgpack.org.

Para usar MessagePack de um cliente da Web do ASP.NET Core SignalR, basta adicionar um parâmetro de mais para o código JavaScript que configura a conexão, como mostrado aqui:

var protocol = new signalR.protocols.msgpack.MessagePackHubProtocol();
var progressConnection = new signalR.HubConnection(
  "/progressDemo",
  {
    transport : signalR.TransportType.WebSocket,
    protocol : protocol
  }
);

Observe que no caso de um cliente Web desejo usar MessagePack, você também deve incluir um arquivo JavaScript separado (signalr msgpackprotocol.min.js) que você pode encontrar no pacote NPM @aspnet/signalr. MessagePack também requer que o servidor do ASP.NET Core a ser configurado, da seguinte forma:

public void ConfigureServices(IServiceCollection services)
{
  // SignalR already configured. Just add this:
  services.AddMessagePackProtocol();
}

Se você usar um cliente não Web, você precisa fazer para habilitar o MessagePack é uma chamada adicional no aplicativo cliente. Especificamente, você pode colocar uma chamada para o método de extensão que withmessagepackprotocol definido na classe HubConnectionBuilder.

Clientes não-Web

O aspecto mais interessante da especificação do .NET padrão é que você pode usar a mesma biblioteca de dentro de uma variedade de aplicativos de cliente como existe compatibilidade de API. A biblioteca de cliente ASP.NET Core SignalR, baseada no .NET 2.0 do padrão, pode ser usada de dentro de qualquer aplicativo cliente compilado para uma variedade de plataformas compatíveis, inclusive o Microsoft .NET Framework 4.6.1 e versões mais recentes. Isso significa que, por exemplo, um aplicativo de Windows Forms compilado em versões do .NET Framework mais recente de 4.6 Felizmente pode consumir os serviços de um hub SignalR do ASP.NET Core.

Com isso em mente, vamos ver como iniciar a tarefa demorada discutida na coluna do mês passado e monitorá-lo de dentro de um novo aplicativo de formulários do Windows. Para começar, depois de criar o esqueleto do aplicativo, referenciar o pacote do NuGet nomeado Microsoft.AspNetCore.SignalR.Client e todas as suas dependências, da seguinte forma:

private static HubConnection _connection;
private async void Form1_Load(object sender, EventArgs e)
{
  _connection = new HubConnectionBuilder()
    .WithUrl("http://localhost:60000/progressdemo")
    .Build();
  await _connection.StartAsync();
}

O código é executado quando o formulário carrega e estabelece a conexão com o ponto de extremidade do SignalR especificado. Se você pretende que troca dados a ser serializado usando o protocolo MessagePack, adicione uma linha mais para o código de configuração do objeto do construtor de conexão, da seguinte maneira:

private async void Form1_Load(object sender, EventArgs e)
{
  _connection = new HubConnectionBuilder()
    .WithUrl("http://localhost:60000/progressdemo")
    .WithMessagePackProtocol()
    .Build();
  await _connection.StartAsync();
}

O objeto de conexão, que você receberá mais deve ser configurado com os manipuladores de cliente responsáveis por atualizar a interface do usuário depois que as notificações do ponto de extremidade SignalR do ASP.NET Core voltar. É aqui que o código:

_connection.On<int>("updateProgressBar", (perc) =>
{
  this.Invoke(
    (Action) (() => label1.Text = String.Format("{0}%", perc))
});

No código de exemplo, quando a notificação updateProgressBar é recebida, um rótulo de texto é atualizado com o valor recebido que representa a porcentagem do trabalho feito no momento. Você pode ter tantos manipuladores como necessidade e o expõe de contraparte SignalR do lado do servidor. Figura 3 mostra a regravação completa do SignalR back-end da última coluna de mês, como um cliente de formulários do Windows deve consumi-lo.

Figura 3 final regravada voltar de SignalR

_connection.On("initProgressBar", () =>
{
  // Run on the UI thread
  Invoke((Action)(() => label1.Text = "0%"));
});
_connection.On<int>("updateProgressBar", (perc) =>
{
  // Run on the UI thread
  Invoke((Action) (() => label1.Text = String.Format("{0}%", perc)));
});
_connection.On("clearProgressBar", () =>
{
  // Run on the UI thread
  Invoke((Action)(() =>
  {
    label1.Text = "100%";
    button1.Enabled = true;
  }));
});
await _connection.StartAsync();

Há alguns problemas que você deve estar atento ao gravar clientes não - Web SignalR. Primeiro, os manipuladores de cliente devem ser totalmente configurados quando a conexão é iniciado. Especificamente, isso significa que a chamada para StartAsync deve ocorrer após todas as chamadas devidas ao método no < T > foram feitas.

Em segundo lugar, tenha em mente que você não deseja executar o lado do servidor — e possivelmente longo — operação SignalR notificará no mesmo thread de interface do usuário do Windows. Que faria o aplicativo cliente não responder. Para contornar o problema, você precisa iniciar a operação do servidor em outro thread, mostrado aqui:

private void button1_Click(object sender, EventArgs e)
{
  Task.Run(() =>
  {
    var client = new WebClient();
    client.UploadString("http://localhost:60000/task/lengthy);
  });
}

Subsequentemente, quando o hub do lado do servidor SignalR notifica novamente, as alterações de vencimento devem transmitidas para o thread de interface do usuário principal antes que eles podem ocorrer. Você pode fazer isso usando o método Invoke no manipulador de cliente. O método Invoke obtém um delegado e é executado no thread da interface do usuário, como mostrado aqui:

Invoke((Action)((perc) =>
  {
    label1.Text = String.Format("{0}%", perc);
  }));

Figura 4 mostra o aplicativo do Windows Forms de exemplo em ação com um rótulo atualizado progressivamente à medida que a operação do servidor faz com que qualquer progresso.

O aplicativo de cliente do Windows Forms atualizado por um Hub de SignalR do ASP.NET Core
Figura 4 do Windows Forms aplicativo de cliente atualizado por um Hub de SignalR do ASP.NET Core

O aplicativo Windows Forms pode receber notificações por meio de qualquer uma das abordagens com suporte: difusão, conexão direta, grupos, usuário único e streaming. Terei mais a dizer sobre isso em uma coluna futura.

Conclusão

O SignalR do ASP.NET Core suporta os mesmos protocolos de transporte como a versão anterior do ASP.NET, incluindo o WebSocket, ServerSentEvents e LongPolling. Além disso, ele oferece suporte a um protocolo de mensagem binária além do formato JSON canônico.

Como seu antecessor, o SignalR do ASP.NET Core pode ser chamado de uma variedade de diferentes clientes, incluindo aplicativos de Windows Forms antigo. A chave para alcançar a ampla compatibilidade é o suporte para a especificação de padrão do .NET 2.0. Como vimos no artigo, se o cliente não é um aplicativo .NET Core, ele deve ser compilado para uma versão do .NET Framework que seja compatível com o padrão mais recente. A versão mínima necessária é .NET Framework 4.6.1.

Certifique-se de check-out do código-fonte deste artigo, que pode ser encontrado em bit.ly/2FxCKTs.


Dino Esposito é autor de mais de 20 livros e de 1.000 artigos em seus 25 anos de carreira. Autor de “The Sabbatical Break”, um show de estilo cênico, Esposito se ocupa escrevendo software para um mundo mais ecológico, como estrategista digital da BaxEnergy. Siga-o no Twitter: @despos.

Agradecemos ao seguinte especialista da Microsoft pela revisão deste artigo: Andrew Stanton-Nurse


Discuta esse artigo no fórum do MSDN Magazine