Este artigo foi traduzido por máquina.

Aresta de corte

Crie uma barra de progresso com o SignalR

Dino Esposito

Baixar o exemplo de código

Dino EspositoNas edições passadas duas desta coluna, discuti como construir um ASP.LÍQUIDO solução para o problema perene de monitoração do progresso de uma tarefa remota do lado do cliente de um aplicativo da Web. Apesar do sucesso e aprovação do AJAX, uma solução abrangente e amplamente aceita para exibir uma barra de progresso sensível ao contexto dentro de um aplicativo da Web sem recorrer ao Silverlight ou Flash ainda está faltando.

Para ser honesto, não há muitas maneiras em que um pode fazer isso. Você pode embarcações de sua própria solução se você quiser, mas o padrão subjacente não vai ser tão diferente do que eu apresentei — especificamente visando ASP.NET MVC — nas colunas anteriores. Este mês, eu estou de volta ao mesmo tema, mas falarei sobre como construir um progresso barra usando uma biblioteca de nova e ainda em andamento: SignalR.

SignalR é um Microsoft.NET Framework biblioteca e jQuery plug-in está sendo desenvolvido pelo ASP.Equipe NET, possivelmente ser incluído no futuro versões do ASP.Plataforma NET. Ele apresenta algumas funcionalidades extremamente promissora que está em falta no momento na.NET Framework e que mais e mais desenvolvedores são exigentes.

SignalR num ápice

SignalR é uma biblioteca de cliente e servidor integrada que permite que os clientes baseados em navegador e ASP.Componentes de servidor baseado na NET para ter uma conversa bidirecional e multistep. Em outras palavras, a conversa não se limita a um intercâmbio de dados de solicitação/resposta única, sem monitoração de Estado; em vez disso, ele continua até fechada explicitamente. A conversa se realiza através de uma conexão persistente e permite que o cliente envia várias mensagens para o servidor e a resposta do servidor — e, muito mais interessante — enviar mensagens assíncronas para o cliente.

Ele deve vir como nenhuma surpresa que a demonstração de canônica que vou usar para ilustrar os principais recursos do SignalR é um aplicativo de bate-papo. O cliente inicia a conversação enviando uma mensagem para o servidor; o servidor — um ASP.Ponto de extremidade de rede — responde e mantém escuta para novas solicitações.

SignalR é especificamente para um cenário de Web e requer jQuery 1.6 (ou mais recente) no cliente e no ASP.NET no servidor. Você pode instalar o SignalR via NuGet ou baixando os bits diretamente do repositório do GitHub em github.com/SignalR/SignalR. Figura 1 mostra a página NuGet com todos os pacotes de SignalR. No mínimo, você precisa baixar o SignalR, que tem dependências em SignalR.Server para a parte de servidor do quadro e SignalR.Js para a parte de cliente da Web do quadro. Os outros pacotes que você pode ver na Figura 1 servir objectivos mais específicos tais como fornecendo uma.Cliente NET, um resolvedor de dependência Ninject e um mecanismo de transporte alternativo baseado em HTML5 Web soquetes.

SignalR Packages Available on the NuGet Platform
Figura 1 SignalR pacotes disponíveis na plataforma do NuGet

Dentro o exemplo de bate-papo

Antes de eu tentar construir uma solução de barra de progresso, que seria útil familiarizar-se com a biblioteca, tendo um olhar para o exemplo de bate-papo, distribuído com o código fonte para download (archive.msdn.microsoft.com/mag201203CuttingEdge) e outras informações referenciadas nos (poucos) posts relacionados atualmente disponíveis na Web. Observe, porém, que a SignalR não é um projeto lançado.

No contexto de um ASP.Projeto NET MVC, você iniciar fazendo referência a um monte de arquivos de script, como mostrado aqui:

    <script src="@Url.Content("~/Scripts/jquery-1.6.4.min.js")"
      type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.signalr.min.js")"
      type="text/javascript"></script>
    <script src="@Url.Content("~/signalr/hubs")"
      type="text/javascript"></script>

Observe que não há nada específico para ASP.NET MVC em SignalR e a biblioteca pode ser usada igualmente bem com aplicativos formulários da Web.

Um ponto interessante de salientar é que os dois primeiros links fazem referência a um arquivo de script específicos. O terceiro link, em vez disso, referências ainda alguns conteúdos de JavaScript, mas que o conteúdo é gerado em tempo real — e que depende de algum outro código que você tem dentro do host ASP.NET aplicativo. Observe também que você precisa da biblioteca JSON2 se você pretende oferecer suporte a versões do Internet Explorer anteriores à versão 8.

Após o carregamento da página, você finalizar a configuração do cliente e abre a conexão. Figura 2 mostra o código que você precisa. Você pode querer chamar esse código de dentro do evento pronto do jQuery. O código vincula manipuladores de script para elementos HTML-JavaScript discreta — e prepara a conversa SignalR.

Figura 2 configuração da biblioteca de SignalR para um exemplo de bate-papo

    <script type="text/javascript">
      $(document).ready(function () {    // Add handler to Send button
        $("#sendButton").click(function () {
          chat.send($('#msg').val());
        });
        // Create a proxy for the server endpoint
        var chat = $.connection.chat; 
        // Add a client-side callback to process any data
        // received from the server
        chat.addMessage = function (message) {
          $('#messages').append('<li>' + message + '</li>');
        };
        // Start the conversation
        $.connection.hub.start();
      });
    </script>

É importante notar que o objeto de conexão $ é definido no arquivo de script SignalR. O objeto de bate-papo, em contraste, é um objeto dinâmico no sentido de que seu código é gerado em tempo real e é injetado na página do cliente por meio a referência de script Hub. O objeto de bate-papo é em última análise, um JavaScript proxy para um objeto de servidor. Neste ponto deve estar claro que o cliente código em Figura 2 significa (e faz) pouco sem contrapartida forte do lado do servidor.

O ASP.NET projeto deve incluir uma referência para o assembly de SignalR e suas dependências, como Microsoft.Web.Infrastructure. O código do lado do servidor inclui um gerenciado classe que corresponda ao objeto de JavaScript que você criou. Com referência a código no Figura 2, você precisará ter um objeto de servidor com a mesma interface como o objeto de bate-papo do lado do cliente. Esta classe de servidor irá herdar da classe de Hub definido no assembly SignalR. Aqui está a assinatura de classe:

using System;
using SignalR.Hubs;
namespace SignalrProgressDemo.Progress
{
  public class Chat : Hub
  {
    public void Send(String message)
    {
      Clients.addMessage(message);
    }
  }
}

Todos os métodos públicos na classe devem corresponder um método JavaScript no cliente. Ou, pelo menos, qualquer método chamado no objeto JavaScript deve ter um método correspondente na classe de servidor. Para que o método Send você ver invocado no código do script do Figura 2 acaba colocando uma chamada para o método Send do objeto bate-papo, tal como definido anteriormente. Para enviar dados para o cliente, o código de servidor usa a propriedade de clientes sobre a classe de Hub. O membro de clientes é do tipo dinâmico, que lhe permite fazer referência dinamicamente dissuadir­minados objetos. Em particular, a propriedade de clientes contém uma referência a um objeto de servidor construído após a interface do objeto cliente: o objeto de bate-papo. Porque o bate-papo objeto em Figura 2 tem um Método AddMessage, o mesmo Método AddMessage é esperado para ser exposto também pelo objeto do lado do servidor de bate-papo.

Em direção a uma Demo de barra de progresso

Agora vamos usar SignalR para criar um sistema de notificação que informa ao cliente qualquer progresso está sendo feita no servidor durante uma tarefa possivelmente longa. Como primeiro passo, vamos criar uma classe de servidor que encapsula a tarefa. O nome que você atribuir a essa classe, enquanto uma escolha arbitrária, afetará o código de cliente que você vai escrever mais tarde. Isso simplesmente significa que você tem mais um motivo para escolher o nome da classe com cuidado. Mais importante ainda, esta classe herdará uma SignalR fornecido classe chamada Hub. Aqui está a assinatura:

public class BookingHub : Hub
{
  ...
}

A classe BookingHub terá alguns métodos públicos — Métodos aceitando qualquer seqüência de parâmetros de entrada que faça sentido para os fins previstos na sua maioria void. Cada método público em uma classe de Hub representa um possível ponto de extremidade para o cliente invocar. Como exemplo, vamos adicionar um método para reservar um voo:

public void BookFlight(String from, String to)
{
  ...
}

Esse método deverá conter toda a lógica que executa a ação determinada (isto é, reserva um voo). O código também irá conter em várias chamadas de fases que, de alguma forma, reportará qualquer progresso volta para o cliente. Digamos que o esqueleto do método que bookflight tem esta aparência:

public void BookFlight(String from, String to)
{
  // Book first leg  var ref1 = BookFlight(from, to);  // Book return flight
  var ref2 = BookFlight(to, from);
  // Handle payment
  PayFlight(ref1, ref2);
}

Em conjunto com estas operações principais, você deseja notificar o usuário sobre os progressos realizados. A classe base de Hub oferece uma propriedade chamada clientes definidos para ser do tipo dinâmico. Em outras palavras, você vai chamar um método neste objeto para chamar voltar o cliente. A forma e a forma deste método, porém, são determinadas pelo próprio cliente. Vamos passar para o cliente, então.

Como mencionado, na página do cliente, você terá um código de script executado quando a página for carregada. Se você usar o jQuery, o evento de .ready $(documento) é um bom local para executar esse código. Em primeiro lugar, você obter um proxy para o objeto de servidor:

var bookingHub = $.connection.bookingHub;
// Some config work
...
// Open the connection
$.connection.hub.start();

O nome do objeto que Reference no componente nativo do SignalR de conexão $ é apenas um proxy criado dinamicamente que expõe a interface pública do objeto BookingHub para o cliente. O proxy é gerado através do link signalr/hubs que têm em <script> seção da página. A Convenção de nomenclatura usada para nomes é camelCase, significando que a classe BookingHub em c# torna-se objeto bookingHub em JavaScript. Este objeto você encontrar métodos que correspondem a interface pública do objeto do servidor. Além disso, para os métodos, a Convenção de nomenclatura usa os mesmos nomes, mas até. Você pode adicionar um manipulador de click para um botão HTML e iniciar uma operação de servidor via AJAX, como mostrado aqui:

bookingHub.bookFlight("fco", "jfk");

Agora você pode definir métodos de cliente para manipular qualquer resposta. Por exemplo, você pode definir o proxy de cliente de um método displayMessage que recebe uma mensagem e exibe-lo através de uma marca span HTML:

bookingHub.displayMessage = function (message) {
  $("#msg").html(message);
};

Observe que você é responsável para a assinatura do visor­Método da mensagem. Você decide o que está sendo passado e que tipo você esperam qualquer entrada para ser.

Para fechar o círculo, há apenas uma última questão: quem está chamando displayMessage e quem é o responsável final pela passagem de dados? É o código do lado do servidor de Hub. Você chamar displayMessage (e qualquer outro método de retorno de chamada que deseja ter no lugar) de dentro do objeto de Hub através do objeto de clientes. Figura 3 mostra a versão final da classe Hub.

Figura 3 A versão Final da classe Hub

public void BookFlight(String from, String to)
{
  // Book first leg
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", from, to));
  Thread.Sleep(2000);
  // Book return
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(3000);
  // Book return
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(2000);
  // Some return value
  Clients.displayMessage("Flight booked successfully.");
}

Observe que neste caso, o nome displayMessage deve corresponder perfeitamente o caso usado no código JavaScript. Se você digitou errado a algo como DisplayMessage, você não vai obter qualquer exceção — mas nenhuma vontade de código executar, tampouco.

O código de Hub é implementado como um objeto de tarefa, para que ele obtém seu próprio thread para executar e não afeta o ASP.Pool de segmentos de rede.

Se um servidor tarefa resulta em trabalho assíncrono está sendo agendado, ele vai pegar um thread do pool de trabalho padrão. A vantagem é, SignalR manipuladores de solicitação são assíncronas, significando que enquanto estão em estado de espera, à espera de novas mensagens, eles não estão usando um thread em tudo. Quando uma mensagem é recebida e não há trabalho a ser feito, um ASP.Thread de trabalho líquido é usado.

Uma barra de progresso verdadeiro com HTML

Em colunas anteriores, bem como em um presente, eu usei a barra de progresso do termo freqüentemente sem nunca mostrar uma barra de indicador clássico como um exemplo de interface do usuário cliente. Ter uma barra de indicador é apenas um belo efeito visual e não requer o código mais complexo na infra-estrutura de async. No entanto, Figura 4 mostra o código JavaScript que cria uma barra de indicador em tempo real dado um valor de porcentagem. Você pode alterar a aparência de elementos HTML através de classes CSS apropriadas.

Figura 4 Criando uma barra de indicador baseado em HTML

var GaugeBar = GaugeBar || {};
GaugeBar.generate = function (percentage) {
  if (typeof (percentage) != "number")
    return;
  if (percentage > 100 || percentage < 0)
    return;
  var colspan = 1;
  var markup = "<table class='gauge-bar-table'><tr>" +
    "<td style='width:" + percentage.toString() +
    "%' class='gauge-bar-completed'></td>";
  if (percentage < 100) {
    markup += "<td class='gauge-bar-tobedone' style='width:" +
      (100 - percentage).toString() +
      "%'></td>";
    colspan++;
  }
  markup += "</tr><tr class='gauge-bar-statusline'><td colspan='" +
    colspan.toString() +
    "'>" +
    percentage.toString() +
    "% completed</td></tr></table>";
  return markup;
}

Você chama esse método de um manipulador de click do botão:

bookingHub.updateGaugeBar = function (perc) {
  $("#bar").html(GaugeBar.generate(perc));
};

O método de updateGaugeBar, portanto, é chamado de um outro método de Hub que apenas usa um retorno de chamada de cliente diferente para relatar o progresso. Você pode substituir apenas displayMessage usado anteriormente com updateGaugeBar dentro de um método de Hub.

Não apenas os clientes da Web

Apresentei SignalR principalmente como uma API que requeira um front-end da Web. Embora este é provavelmente o mais atraente cenário em que você pode querer usá-lo, SignalR está em nenhuma maneira limitada para suportar apenas os clientes da Web. Você pode fazer o download de um cliente para o.NET aplicativos de desktop e outro cliente serão lançado em breve para oferecer suporte a clientes de Windows Phone.

Esta coluna apenas arranhou a superfície de SignalR no sentido de que ele apresentou a abordagem mais simples e eficaz que programá-lo. Em uma coluna futura, eu vou investigar alguns da magia que ele faz sob o capô e como os pacotes são movidos ao longo do fio. Fique atento.

Dino Esposito é o autor de “Programming Microsoft ASP.NET MVC3” (Microsoft Press, 2011) e coautor de “Microsoft .NET: Arquitetura de aplicações para a empresa"(Microsoft Press, 2008). Residente na Itália, ele é um orador freqüente em eventos do setor em todo o mundo. Você pode segui-lo no Twitter em twitter.com/despos.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Damian Edwards