Guia da API dos Hubs do ASP.NET SignalR – Servidor (C#)

por Patrick Fletcher, Tom Dykstra

Aviso

Esta documentação não é para a versão mais recente do SignalR. Dê uma olhada em ASP.NET Core SignalR.

Este documento fornece uma introdução à programação do lado do servidor da API dos Hubs do ASP.NET SignalR para SignalR versão 2, com exemplos de código demonstrando opções comuns.

A API dos Hubs do SignalR permite que você faça RPCs (chamadas de procedimento remoto) de um servidor para clientes conectados e de clientes para o servidor. No código do servidor, você define métodos que podem ser chamados por clientes e chama métodos que são executados no cliente. No código do cliente, você define métodos que podem ser chamados do servidor e chama métodos executados no servidor. O SignalR cuida de todos os encanamentos de cliente para servidor para você.

O SignalR também oferece uma API de nível inferior chamada Conexões Persistentes. Para obter uma introdução ao SignalR, hubs e conexões persistentes, consulte Introdução ao SignalR 2.

Versões de software usadas neste tópico

Versões do tópico

Para obter informações sobre versões anteriores do SignalR, consulte Versões mais antigas do SignalR.

Perguntas e comentários

Deixe comentários sobre como você gostou deste tutorial e o que poderíamos melhorar nos comentários na parte inferior da página. Se você tiver dúvidas que não estão diretamente relacionadas ao tutorial, poderá postá-las no fórum do ASP.NET SignalR ou StackOverflow.com.

Visão geral

Este documento contém as seguintes seções:

Para obter a documentação sobre como programar clientes, consulte os seguintes recursos:

Os componentes do servidor para o SignalR 2 só estão disponíveis no .NET 4.5. Os servidores que executam o .NET 4.0 devem usar o SignalR v1.x.

Como registrar o middleware do SignalR

Para definir a rota que os clientes usarão para se conectar ao Hub, chame o MapSignalR método quando o aplicativo for iniciado. MapSignalR é um método de extensão para a OwinExtensions classe . O exemplo a seguir mostra como definir a rota dos Hubs do SignalR usando uma classe de inicialização OWIN.

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }

    }
}

Se você estiver adicionando a funcionalidade do SignalR a um aplicativo MVC ASP.NET, verifique se a rota do SignalR foi adicionada antes das outras rotas. Para obter mais informações, consulte Tutorial: Introdução com SignalR 2 e MVC 5.

A URL /signalr

Por padrão, a URL de rota que os clientes usarão para se conectar ao Hub é "/signalr". (Não confunda essa URL com a URL "/signalr/hubs", que é para o arquivo JavaScript gerado automaticamente. Para obter mais informações sobre o proxy gerado, consulte Guia da API dos Hubs do SignalR – Cliente JavaScript – O proxy gerado e o que ele faz por você.)

Pode haver circunstâncias extraordinárias que tornam essa URL base inutilizável para o SignalR; por exemplo, você tem uma pasta em seu projeto chamada signalr e não deseja alterar o nome. Nesse caso, você pode alterar a URL base, conforme mostrado nos exemplos a seguir (substitua "/signalr" no código de exemplo pela URL desejada).

Código do servidor que especifica a URL

app.MapSignalR("/signalr", new HubConfiguration());

Código do cliente JavaScript que especifica a URL (com o proxy gerado)

$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);

Código do cliente JavaScript que especifica a URL (sem o proxy gerado)

var connection = $.hubConnection("/signalr", { useDefaultPath: false });

Código do cliente .NET que especifica a URL

var hubConnection = new HubConnection("http://contoso.com/signalr", useDefaultUrl: false);

Configurando opções do SignalR

As sobrecargas do MapSignalR método permitem que você especifique uma URL personalizada, um resolvedor de dependência personalizado e as seguintes opções:

  • Habilite chamadas entre domínios usando CORS ou JSONP de clientes do navegador.

    Normalmente, se o navegador carregar uma página do http://contoso.com, a conexão do SignalR estará no mesmo domínio, em http://contoso.com/signalr. Se a página de http://contoso.com fizer uma conexão com http://fabrikam.com/signalr, essa será uma conexão entre domínios. Por motivos de segurança, as conexões entre domínios são desabilitadas por padrão. Para obter mais informações, consulte ASP.NET Guia da API dos Hubs do SignalR – Cliente JavaScript – Como estabelecer uma conexão entre domínios.

  • Habilite mensagens de erro detalhadas.

    Quando ocorrem erros, o comportamento padrão do SignalR é enviar aos clientes uma mensagem de notificação sem detalhes sobre o que aconteceu. O envio de informações detalhadas de erro para clientes não é recomendado na produção, pois usuários mal-intencionados podem usar as informações em ataques contra seu aplicativo. Para solução de problemas, você pode usar essa opção para habilitar temporariamente relatórios de erros mais informativos.

  • Desabilitar arquivos de proxy JavaScript gerados automaticamente.

    Por padrão, um arquivo JavaScript com proxies para suas classes de Hub é gerado em resposta à URL "/signalr/hubs". Se você não quiser usar os proxies JavaScript ou se quiser gerar esse arquivo manualmente e se referir a um arquivo físico em seus clientes, poderá usar essa opção para desabilitar a geração de proxy. Para obter mais informações, consulte Guia da API dos Hubs do SignalR – Cliente JavaScript – Como criar um arquivo físico para o proxy gerado pelo SignalR.

O exemplo a seguir mostra como especificar a URL de conexão do SignalR e essas opções em uma chamada para o MapSignalR método . Para especificar uma URL personalizada, substitua "/signalr" no exemplo pela URL que você deseja usar.

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);

Como criar e usar classes hub

Para criar um Hub, crie uma classe derivada de Microsoft.Aspnet.Signalr.Hub. O exemplo a seguir mostra uma classe hub simples para um aplicativo de chat.

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}

Neste exemplo, um cliente conectado pode chamar o NewContosoChatMessage método e, quando isso acontece, os dados recebidos são transmitidos para todos os clientes conectados.

Tempo de vida do objeto hub

Você não cria uma instância da classe Hub nem chama seus métodos do seu próprio código no servidor; tudo o que é feito para você pelo pipeline dos Hubs do SignalR. O SignalR cria uma nova instância da classe Hub sempre que precisa lidar com uma operação de Hub, como quando um cliente se conecta, desconecta ou faz uma chamada de método para o servidor.

Como as instâncias da classe Hub são transitórias, você não pode usá-las para manter o estado de uma chamada de método para a próxima. Sempre que o servidor recebe uma chamada de método de um cliente, uma nova instância da classe Hub processa a mensagem. Para manter o estado por meio de várias conexões e chamadas de método, use algum outro método, como um banco de dados ou uma variável estática na classe Hub, ou uma classe diferente que não deriva de Hub. Se você persistir dados na memória, usando um método como uma variável estática na classe Hub, os dados serão perdidos quando o domínio do aplicativo for reciclado.

Se você quiser enviar mensagens para clientes de seu próprio código executado fora da classe Hub, não poderá fazer isso instanciando uma instância de classe hub, mas pode fazer isso obtendo uma referência ao objeto de contexto SignalR para sua classe Hub. Para obter mais informações, consulte Como chamar métodos de cliente e gerenciar grupos de fora da classe Hub mais adiante neste tópico.

Uso de maiúsculas e minúsculas de nomes de Hub em clientes JavaScript

Por padrão, os clientes JavaScript referem-se aos Hubs usando uma versão com maiúsculas e minúsculas do nome da classe. O SignalR faz essa alteração automaticamente para que o código JavaScript possa estar em conformidade com as convenções do JavaScript. O exemplo anterior seria conhecido como contosoChatHub no código JavaScript.

Servidor

public class ContosoChatHub : Hub

Cliente JavaScript usando proxy gerado

var contosoChatHubProxy = $.connection.contosoChatHub;

Se você quiser especificar um nome diferente para os clientes usarem, adicione o HubName atributo . Quando você usa um HubName atributo, não há nenhuma alteração de nome para o caso camel em clientes JavaScript.

Servidor

[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub

Cliente JavaScript usando proxy gerado

var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

Vários Hubs

Você pode definir várias classes de Hub em um aplicativo. Quando você faz isso, a conexão é compartilhada, mas os grupos são separados:

  • Todos os clientes usarão a mesma URL para estabelecer uma conexão signalr com seu serviço ("/signalr" ou sua URL personalizada, se você especificou um), e essa conexão é usada para todos os Hubs definidos pelo serviço.

    Não há diferença de desempenho para vários Hubs em comparação com a definição de todas as funcionalidades do Hub em uma única classe.

  • Todos os Hubs obtêm as mesmas informações de solicitação HTTP.

    Como todos os Hubs compartilham a mesma conexão, as únicas informações de solicitação HTTP que o servidor obtém é o que vem na solicitação HTTP original que estabelece a conexão SignalR. Se você usar a solicitação de conexão para passar informações do cliente para o servidor especificando uma cadeia de caracteres de consulta, não poderá fornecer cadeias de caracteres de consulta diferentes para hubs diferentes. Todos os Hubs receberão as mesmas informações.

  • O arquivo de proxies JavaScript gerado conterá proxies para todos os Hubs em um arquivo.

    Para obter informações sobre proxies JavaScript, consulte Guia da API dos Hubs do SignalR – Cliente JavaScript – O proxy gerado e o que ele faz por você.

  • Os grupos são definidos em Hubs.

    No SignalR, você pode definir grupos nomeados para difundir para subconjuntos de clientes conectados. Os grupos são mantidos separadamente para cada Hub. Por exemplo, um grupo chamado "Administradores" incluiria um conjunto de clientes para sua ContosoChatHub classe e o mesmo nome de grupo se referiria a um conjunto diferente de clientes para sua StockTickerHub classe.

Hubs Strongly-Typed

Para definir uma interface para os métodos de hub que seu cliente pode referenciar (e habilitar o Intellisense em seus métodos de hub), derive seu hub de Hub<T> (introduzido no SignalR 2.1) em vez de Hub:

public class StrongHub : Hub<IClient>
{
    public async Task Send(string message)
    {
        await Clients.All.NewMessage(message);
    }
}

public interface IClient
{
    Task NewMessage(string message);
}

Como definir métodos na classe Hub que os clientes podem chamar

Para expor um método no Hub que você deseja chamar do cliente, declare um método público, conforme mostrado nos exemplos a seguir.

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}
public class StockTickerHub : Hub
{
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
}

Você pode especificar um tipo de retorno e parâmetros, incluindo tipos complexos e matrizes, como faria em qualquer método C#. Todos os dados recebidos em parâmetros ou retornados ao chamador são comunicados entre o cliente e o servidor usando JSON, e o SignalR manipula a associação de objetos complexos e matrizes de objetos automaticamente.

Uso de maiúsculas e minúsculas de nomes de método em clientes JavaScript

Por padrão, os clientes JavaScript referem-se aos métodos hub usando uma versão com maiúsculas e minúsculas do nome do método. O SignalR faz essa alteração automaticamente para que o código JavaScript possa estar em conformidade com as convenções do JavaScript.

Servidor

public void NewContosoChatMessage(string userName, string message)

Cliente JavaScript usando proxy gerado

contosoChatHubProxy.server.newContosoChatMessage(userName, message);

Se você quiser especificar um nome diferente para os clientes usarem, adicione o HubMethodName atributo .

Servidor

[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)

Cliente JavaScript usando proxy gerado

contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

Quando executar de forma assíncrona

Se o método for de execução prolongada ou tiver que fazer um trabalho que envolva espera, como uma pesquisa de banco de dados ou uma chamada de serviço Web, torne o método Hub assíncrono retornando uma Tarefa (no lugar do void retorno) ou um objeto Task<T> (no lugar do tipo de T retorno). Quando você retorna um Task objeto do método , o SignalR aguarda a Task conclusão e envia o resultado desembrulhado de volta para o cliente, portanto, não há diferença na forma como você codifica a chamada de método no cliente.

Tornar um método hub assíncrono evita bloquear a conexão quando ele usa o transporte WebSocket. Quando um método Hub é executado de forma síncrona e o transporte é WebSocket, as invocações subsequentes de métodos no Hub do mesmo cliente são bloqueadas até que o método Hub seja concluído.

O exemplo a seguir mostra o mesmo método codificado para ser executado de forma síncrona ou assíncrona, seguido pelo código do cliente JavaScript que funciona para chamar qualquer versão.

Síncrono

public IEnumerable<Stock> GetAllStocks()
{
    // Returns data from memory.
    return _stockTicker.GetAllStocks();
}

Assíncronos

public async Task<IEnumerable<Stock>> GetAllStocks()
{
    // Returns data from a web service.
    var uri = Util.getServiceUri("Stocks");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<IEnumerable<Stock>>());
    }
}

Cliente JavaScript usando proxy gerado

stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
    $.each(stocks, function () {
        alert(this.Symbol + ' ' + this.Price);
    });
});

Para obter mais informações sobre como usar métodos assíncronos no ASP.NET 4.5, consulte Usando métodos assíncronos no ASP.NET MVC 4.

Definindo sobrecargas

Se você quiser definir sobrecargas para um método, o número de parâmetros em cada sobrecarga deve ser diferente. Se você diferenciar uma sobrecarga apenas especificando diferentes tipos de parâmetro, sua classe Hub será compilada, mas o serviço SignalR lançará uma exceção em tempo de execução quando os clientes tentarem chamar uma das sobrecargas.

Relatar o progresso das invocações de método de hub

O SignalR 2.1 adiciona suporte para o padrão de relatório de progresso introduzido no .NET 4.5. Para implementar relatórios de progresso, defina um IProgress<T> parâmetro para o método de hub que seu cliente pode acessar:

public class ProgressHub : Hub
{
    public async Task<string> DoLongRunningThing(IProgress<int> progress)
    {
        for (int i = 0; i <= 100; i+=5)
        {
            await Task.Delay(200);
            progress.Report(i);
        }
        return "Job complete!";
    }
}

Ao escrever um método de servidor de longa execução, é importante usar um padrão de programação assíncrona como Async/Await em vez de bloquear o thread do hub.

Como chamar métodos de cliente da classe Hub

Para chamar métodos de cliente do servidor, use a Clients propriedade em um método em sua classe Hub. O exemplo a seguir mostra o código do servidor que chama addNewMessageToPage todos os clientes conectados e o código do cliente que define o método em um cliente JavaScript.

Servidor

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}

Invocar um método cliente é uma operação assíncrona e retorna um Task. Use await:

  • Para garantir que a mensagem seja enviada sem erro.
  • Para habilitar a captura e o tratamento de erros em um bloco try-catch.

Cliente JavaScript usando proxy gerado

contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
    // Add the message to the page. 
    $('#discussion').append('<li><strong>' + htmlEncode(name)
        + '</strong>: ' + htmlEncode(message) + '<li>');
};

Você não pode obter um valor retornado de um método cliente; sintaxe como int x = Clients.All.add(1,1) não funciona.

Você pode especificar tipos complexos e matrizes para os parâmetros. O exemplo a seguir passa um tipo complexo para o cliente em um parâmetro de método.

Código do servidor que chama um método cliente usando um objeto complexo

public async Task SendMessage(string name, string message)
{
    await Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
}

Código do servidor que define o objeto complexo

public class ContosoChatMessage
{
    public string UserName { get; set; }
    public string Message { get; set; }
}

Cliente JavaScript usando proxy gerado

var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
    console.log(message.UserName + ' ' + message.Message);
});

Selecionando quais clientes receberão o RPC

A propriedade Clients retorna um objeto HubConnectionContext que fornece várias opções para especificar quais clientes receberão o RPC:

  • Todos os clientes conectados.

    Clients.All.addContosoChatMessageToPage(name, message);
    
  • Somente o cliente de chamada.

    Clients.Caller.addContosoChatMessageToPage(name, message);
    
  • Todos os clientes, exceto o cliente de chamada.

    Clients.Others.addContosoChatMessageToPage(name, message);
    
  • Um cliente específico identificado pela ID de conexão.

    Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
    

    Este exemplo chama addContosoChatMessageToPage o cliente de chamada e tem o mesmo efeito que usar Clients.Caller.

  • Todos os clientes conectados, exceto os clientes especificados, identificados pela ID de conexão.

    Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • Todos os clientes conectados em um grupo especificado.

    Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • Todos os clientes conectados em um grupo especificado, exceto os clientes especificados, identificados pela ID de conexão.

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • Todos os clientes conectados em um grupo especificado, exceto o cliente de chamada.

    Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);
    
  • Um usuário específico, identificado por userId.

    Clients.User(userid).addContosoChatMessageToPage(name, message);
    

    Por padrão, isso é IPrincipal.Identity.Name, mas isso pode ser alterado registrando uma implementação de IUserIdProvider com o host global.

  • Todos os clientes e grupos em uma lista de IDs de conexão.

    Clients.Clients(ConnectionIds).broadcastMessage(name, message);
    
  • Uma lista de grupos.

    Clients.Groups(GroupIds).broadcastMessage(name, message);
    
  • Um usuário por nome.

    Clients.Client(username).broadcastMessage(name, message);
    
  • Uma lista de nomes de usuário (introduzida no SignalR 2.1).

    Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);
    

Nenhuma validação em tempo de compilação para nomes de método

O nome do método especificado é interpretado como um objeto dinâmico, o que significa que não há nenhum IntelliSense ou validação em tempo de compilação para ele. A expressão é avaliada em tempo de execução. Quando a chamada de método é executada, o SignalR envia o nome do método e os valores de parâmetro para o cliente e, se o cliente tiver um método que corresponda ao nome, esse método será chamado e os valores de parâmetro serão passados para ele. Se nenhum método correspondente for encontrado no cliente, nenhum erro será gerado. Para obter informações sobre o formato dos dados que o SignalR transmite para o cliente nos bastidores quando você chama um método de cliente, consulte Introdução ao SignalR.

Correspondência de nome de método que não diferencia maiúsculas de minúsculas

A correspondência de nome de método não diferencia maiúsculas de minúsculas. Por exemplo, Clients.All.addContosoChatMessageToPage no servidor executará AddContosoChatMessageToPage, addcontosochatmessagetopageou addContosoChatMessageToPage no cliente.

Execução assíncrona

O método que você chama é executado de forma assíncrona. Qualquer código que venha após uma chamada de método para um cliente será executado imediatamente sem esperar que o SignalR conclua a transmissão de dados para clientes, a menos que você especifique que as linhas de código subsequentes devem aguardar a conclusão do método. O exemplo de código a seguir mostra como executar dois métodos de cliente sequencialmente.

Usando Await (.NET 4.5)

public async Task NewContosoChatMessage(string name, string message)
{
    await Clients.Others.addContosoChatMessageToPage(data);
    await Clients.Caller.notifyMessageSent();
}

Se você usar await para aguardar até que um método de cliente seja concluído antes da próxima linha de código ser executada, isso não significa que os clientes realmente receberão a mensagem antes que a próxima linha de código seja executada. "Conclusão" de uma chamada de método de cliente significa apenas que o SignalR fez tudo o que era necessário para enviar a mensagem. Se você precisar verificar se os clientes receberam a mensagem, será necessário programar esse mecanismo por conta própria. Por exemplo, você pode codificar um MessageReceived método no Hub e, no addContosoChatMessageToPage método no cliente, você pode chamar MessageReceived depois de fazer qualquer trabalho que precise fazer no cliente. No MessageReceived Hub, você pode fazer qualquer trabalho que dependa da recepção real do cliente e do processamento da chamada de método original.

Como usar uma variável de cadeia de caracteres como o nome do método

Se você quiser invocar um método de cliente usando uma variável de cadeia de caracteres como o nome do método, converta Clients.All (ou Clients.Others, Clients.Calleretc.) para IClientProxy e chame Invoke(methodName, args...).

public async Task NewContosoChatMessage(string name, string message)
{
    string methodToCall = "addContosoChatMessageToPage";
    IClientProxy proxy = Clients.All;
    await proxy.Invoke(methodToCall, name, message);
}

Como gerenciar a associação de grupo da classe Hub

Os grupos no SignalR fornecem um método para transmitir mensagens para subconjuntos especificados de clientes conectados. Um grupo pode ter qualquer número de clientes e um cliente pode ser membro de qualquer número de grupos.

Para gerenciar a associação de grupo, use os métodos Add e Remove fornecidos pela Groups propriedade da classe Hub. O exemplo a seguir mostra os Groups.Add métodos e Groups.Remove usados em métodos hub que são chamados pelo código do cliente, seguidos pelo código do cliente JavaScript que os chama.

Servidor

public class ContosoChatHub : Hub
{
    public Task JoinGroup(string groupName)
    {
        return Groups.Add(Context.ConnectionId, groupName);
    }

    public Task LeaveGroup(string groupName)
    {
        return Groups.Remove(Context.ConnectionId, groupName);
    }
}

Cliente JavaScript usando proxy gerado

contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);

Você não precisa criar grupos explicitamente. Na verdade, um grupo é criado automaticamente na primeira vez que você especifica seu nome em uma chamada para Groups.Adde é excluído quando você remove a última conexão da associação nele.

Não há nenhuma API para obter uma lista de associação de grupo ou uma lista de grupos. O SignalR envia mensagens para clientes e grupos com base em um modelo pub/sub e o servidor não mantém listas de grupos ou associações de grupo. Isso ajuda a maximizar a escalabilidade, pois sempre que você adiciona um nó a um web farm, qualquer estado que o SignalR mantém precisa ser propagado para o novo nó.

Execução assíncrona dos métodos Add e Remove

Os Groups.Add métodos e Groups.Remove são executados de forma assíncrona. Se você quiser adicionar um cliente a um grupo e enviar imediatamente uma mensagem ao cliente usando o grupo , precisará garantir que o Groups.Add método seja concluído primeiro. O exemplo de código a seguir mostra como fazer isso.

Adicionar um cliente a um grupo e, em seguida, enviar mensagens a esse cliente

public async Task JoinGroup(string groupName)
{
    await Groups.Add(Context.ConnectionId, groupName);
    await Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}

Persistência de associação de grupo

O SignalR rastreia conexões, não usuários, portanto, se você quiser que um usuário esteja no mesmo grupo sempre que o usuário estabelecer uma conexão, será necessário chamar Groups.Add sempre que o usuário estabelecer uma nova conexão.

Após uma perda temporária de conectividade, às vezes o SignalR pode restaurar a conexão automaticamente. Nesse caso, o SignalR está restaurando a mesma conexão, não estabelecendo uma nova conexão e, portanto, a associação de grupo do cliente é restaurada automaticamente. Isso é possível mesmo quando a interrupção temporária é o resultado de uma reinicialização ou falha do servidor, pois o estado de conexão para cada cliente, incluindo associações de grupo, é arredondado para o cliente. Se um servidor ficar inativo e for substituído por um novo servidor antes do tempo limite da conexão, um cliente poderá se reconectar automaticamente ao novo servidor e se registrar novamente em grupos dos quais ele é membro.

Quando uma conexão não pode ser restaurada automaticamente após uma perda de conectividade ou quando a conexão atinge o tempo limite ou quando o cliente se desconecta (por exemplo, quando um navegador navega para uma nova página), as associações de grupo são perdidas. A próxima vez que o usuário se conectar será uma nova conexão. Para manter associações de grupo quando o mesmo usuário estabelece uma nova conexão, seu aplicativo precisa acompanhar as associações entre usuários e grupos e restaurar associações de grupo sempre que um usuário estabelecer uma nova conexão.

Para obter mais informações sobre conexões e reconexões, consulte Como lidar com eventos de tempo de vida da conexão na classe Hub mais adiante neste tópico.

Grupos de usuário único

Os aplicativos que usam o SignalR normalmente precisam acompanhar as associações entre usuários e conexões para saber qual usuário enviou uma mensagem e quais usuários devem receber uma mensagem. Os grupos são usados em um dos dois padrões comumente usados para fazer isso.

  • Grupos de usuário único.

    Você pode especificar o nome de usuário como o nome de grupo e adicionar a ID de conexão atual ao grupo sempre que o usuário se conectar ou se reconectar. Para enviar mensagens para o usuário que você envia para o grupo. Uma desvantagem desse método é que o grupo não fornece uma maneira de descobrir se o usuário está online ou offline.

  • Acompanhe as associações entre nomes de usuário e IDs de conexão.

    Você pode armazenar uma associação entre cada nome de usuário e uma ou mais IDs de conexão em um dicionário ou banco de dados e atualizar os dados armazenados sempre que o usuário se conectar ou desconectar. Para enviar mensagens ao usuário, especifique as IDs de conexão. Uma desvantagem desse método é que ele usa mais memória.

Como lidar com eventos de tempo de vida da conexão na classe Hub

Os motivos típicos para lidar com eventos de tempo de vida da conexão são controlar se um usuário está conectado ou não e acompanhar a associação entre nomes de usuário e IDs de conexão. Para executar seu próprio código quando os clientes se conectarem ou se desconectarem, substitua os OnConnectedmétodos virtuais , OnDisconnectede OnReconnected da classe Hub, conforme mostrado no exemplo a seguir.

public class ContosoChatHub : Hub
{
    public override Task OnConnected()
    {
        // Add your own code here.
        // For example: in a chat application, record the association between
        // the current connection ID and user name, and mark the user as online.
        // After the code in this method completes, the client is informed that
        // the connection is established; for example, in a JavaScript client,
        // the start().done callback is executed.
        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        // Add your own code here.
        // For example: in a chat application, mark the user as offline, 
        // delete the association between the current connection id and user name.
        return base.OnDisconnected(stopCalled);
    }

    public override Task OnReconnected()
    {
        // Add your own code here.
        // For example: in a chat application, you might have marked the
        // user as offline after a period of inactivity; in that case 
        // mark the user as online again.
        return base.OnReconnected();
    }
}

Quando OnConnected, OnDisconnected e OnReconnected são chamados

Cada vez que um navegador navega para uma nova página, uma nova conexão precisa ser estabelecida, o que significa que o SignalR executará o OnDisconnected método seguido pelo OnConnected método . O SignalR sempre cria uma nova ID de conexão quando uma nova conexão é estabelecida.

O OnReconnected método é chamado quando há uma interrupção temporária na conectividade da qual o SignalR pode se recuperar automaticamente, como quando um cabo é temporariamente desconectado e reconectado antes do tempo limite da conexão. O OnDisconnected método é chamado quando o cliente é desconectado e o SignalR não pode se reconectar automaticamente, como quando um navegador navega para uma nova página. Portanto, uma possível sequência de eventos para um determinado cliente é OnConnected, OnReconnected, OnDisconnectedou OnConnected. OnDisconnected Você não verá a sequência OnConnected, OnDisconnected, OnReconnected para uma determinada conexão.

O OnDisconnected método não é chamado em alguns cenários, como quando um servidor fica inativo ou o Domínio do Aplicativo é reciclado. Quando outro servidor entra em linha ou o Domínio do Aplicativo conclui sua reciclagem, alguns clientes podem ser capazes de reconectar e disparar o OnReconnected evento.

Para obter mais informações, consulte Noções básicas e tratamento de eventos de tempo de vida da conexão no SignalR.

Estado do chamador não preenchido

Os métodos do manipulador de eventos de tempo de vida da conexão são chamados do servidor, o que significa que qualquer estado que você colocar no state objeto no cliente não será preenchido na Caller propriedade no servidor. Para obter informações sobre o state objeto e a Caller propriedade , consulte Como passar o estado entre clientes e a classe Hub mais adiante neste tópico.

Como obter informações sobre o cliente da propriedade Context

Para obter informações sobre o cliente, use a Context propriedade da classe Hub. A Context propriedade retorna um objeto HubCallerContext que fornece acesso às seguintes informações:

  • A ID de conexão do cliente de chamada.

    string connectionID = Context.ConnectionId;
    

    A ID de conexão é um GUID atribuído pelo SignalR (você não pode especificar o valor em seu próprio código). Há uma ID de conexão para cada conexão e a mesma ID de conexão é usada por todos os Hubs se você tiver vários Hubs em seu aplicativo.

  • Dados de cabeçalho HTTP.

    System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
    

    Você também pode obter cabeçalhos HTTP de Context.Headers. O motivo para várias referências à mesma coisa é que Context.Headers foi criado primeiro, a Context.Request propriedade foi adicionada posteriormente e Context.Headers foi mantida para compatibilidade com versões anteriores.

  • Consultar dados de cadeia de caracteres.

    System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString;
    string parameterValue = queryString["parametername"]
    

    Você também pode obter dados de cadeia de caracteres de consulta de Context.QueryString.

    A cadeia de caracteres de consulta que você obtém nessa propriedade é aquela que foi usada com a solicitação HTTP que estabeleceu a conexão do SignalR. Você pode adicionar parâmetros de cadeia de caracteres de consulta no cliente configurando a conexão, que é uma maneira conveniente de passar dados sobre o cliente do cliente para o servidor. O exemplo a seguir mostra uma maneira de adicionar uma cadeia de caracteres de consulta em um cliente JavaScript quando você usa o proxy gerado.

    $.connection.hub.qs = { "version" : "1.0" };
    

    Para obter mais informações sobre como definir parâmetros de cadeia de caracteres de consulta, consulte os guias de API para os clientes JavaScript e .NET .

    Você pode encontrar o método de transporte usado para a conexão nos dados da cadeia de caracteres de consulta, juntamente com alguns outros valores usados internamente pelo SignalR:

    string transportMethod = queryString["transport"];
    

    O valor de transportMethod será "webSockets", "serverSentEvents", "foreverFrame" ou "longPolling". Observe que, se você marcar esse valor no método do OnConnected manipulador de eventos, em alguns cenários, você poderá obter inicialmente um valor de transporte que não é o método de transporte negociado final para a conexão. Nesse caso, o método lançará uma exceção e será chamado novamente mais tarde quando o método de transporte final for estabelecido.

  • Cookies.

    System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;
    

    Você também pode obter cookies de Context.RequestCookies.

  • Informação do usuário.

    System.Security.Principal.IPrincipal user = Context.User;
    
  • O objeto HttpContext para a solicitação :

    System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();
    

    Use esse método em vez de obter HttpContext.Current o HttpContext objeto para a conexão do SignalR.

Como passar o estado entre clientes e a classe Hub

O proxy do cliente fornece um state objeto no qual você pode armazenar dados que você deseja transmitir para o servidor com cada chamada de método. No servidor, você pode acessar esses dados na Clients.Caller propriedade em métodos hub que são chamados por clientes. A Clients.Caller propriedade não é preenchida para os métodos OnConnecteddo manipulador de eventos de tempo de vida da conexão , OnDisconnectede OnReconnected.

Criar ou atualizar dados no state objeto e a Clients.Caller propriedade funciona em ambas as direções. Você pode atualizar os valores no servidor e eles são passados de volta para o cliente.

O exemplo a seguir mostra o código do cliente JavaScript que armazena o estado para transmissão para o servidor com cada chamada de método.

contosoChatHubProxy.state.userName = "Fadi Fakhouri";
contosoChatHubProxy.state.computerName = "fadivm1";

O exemplo a seguir mostra o código equivalente em um cliente .NET.

contosoChatHubProxy["userName"] = "Fadi Fakhouri";
chatHubProxy["computerName"] = "fadivm1";

Na classe Hub, você pode acessar esses dados na Clients.Caller propriedade . O exemplo a seguir mostra o código que recupera o estado referenciado no exemplo anterior.

public async Task NewContosoChatMessage(string data)
{
    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    await Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}

Observação

Esse mecanismo para manter o estado não se destina a grandes quantidades de dados, pois tudo o state que você coloca na propriedade ou Clients.Caller é arredondado com cada invocação de método. Ele é útil para itens menores, como nomes de usuário ou contadores.

Em VB.NET ou em um hub fortemente tipado, o objeto de estado do chamador não pode ser acessado por meio Clients.Callerde ; em vez disso, use Clients.CallerState (introduzido no SignalR 2.1):

Usando CallerState em C#

public async Task NewContosoChatMessage(string data)
{
    string userName = Clients.CallerState.userName;
    string computerName = Clients.CallerState.computerName;
    await Clients.Others.addContosoChatMessageToPage(data, userName, computerName);
}

Usando CallerState no Visual Basic

Public Async Function NewContosoChatMessage(message As String) As Task
    Dim userName As String = Clients.CallerState.userName
    Dim computerName As String = Clients.CallerState.computerName
    Await Clients.Others.addContosoChatMessageToPage(message, userName, computerName)
End Sub

Como lidar com erros na classe Hub

Para lidar com erros que ocorrem em seus métodos de classe hub, primeiro certifique-se de "observar" quaisquer exceções de operações assíncronas (como invocar métodos de cliente) usando await. Em seguida, use um ou mais dos seguintes métodos:

  • Encapsule o código do método em blocos try-catch e registre o objeto de exceção. Para fins de depuração, você pode enviar a exceção ao cliente, mas, por motivos de segurança, não é recomendável enviar informações detalhadas aos clientes em produção.

  • Crie um módulo de pipeline de Hubs que manipula o método OnIncomingError . O exemplo a seguir mostra um módulo de pipeline que registra erros, seguido pelo código em Startup.cs que injeta o módulo no pipeline hubs.

    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
        {
            Debug.WriteLine("=> Exception " + exceptionContext.Error.Message);
            if (exceptionContext.Error.InnerException != null)
            {
                Debug.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message);
            }
            base.OnIncomingError(exceptionContext, invokerContext);
    
        }
    }
    
    public void Configuration(IAppBuilder app)
    {
        // Any connection or hub wire up and configuration should go here
        GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
        app.MapSignalR();
    }
    
  • Use a HubException classe (introduzida no SignalR 2). Esse erro pode ser gerado de qualquer invocação de hub. O HubError construtor usa uma mensagem de cadeia de caracteres e um objeto para armazenar dados de erro extras. O SignalR serializará automaticamente a exceção e a enviará ao cliente, onde ela será usada para rejeitar ou falhar a invocação do método de hub.

    Os exemplos de código a seguir demonstram como gerar um HubException durante uma invocação de Hub e como lidar com a exceção em clientes JavaScript e .NET.

    Código do servidor que demonstra a classe HubException

    public class MyHub : Hub
    {
        public async Task Send(string message)
        {
            if(message.Contains("<script>"))
            {
                throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message });
            }
    
            await Clients.All.send(message);
        }
    }
    

    Código do cliente JavaScript demonstrando a resposta ao lançamento de uma HubException em um hub

    myHub.server.send("<script>")
                .fail(function (e) {
                    if (e.source === 'HubException') {
                        console.log(e.message + ' : ' + e.data.user);
                    }
                });
    

    Código do cliente .NET que demonstra a resposta ao lançamento de uma HubException em um hub

    try
    {
        await myHub.Invoke("Send", "<script>");
    }
    catch(HubException ex)
    {
        Console.WriteLine(ex.Message);
    }
    

Para obter mais informações sobre os módulos de pipeline do Hub, consulte Como personalizar o pipeline de Hubs mais adiante neste tópico.

Como habilitar o rastreamento

Para habilitar o rastreamento do lado do servidor, adicione um sistema. diagnóstico elemento ao arquivo Web.config, conforme mostrado neste exemplo:

<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit https://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="SignalRSamples" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;" />
    <add name="SignalRSamplesWithMARS" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;MultipleActiveResultSets=true;" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <system.diagnostics>
    <sources>
      <source name="SignalR.SqlMessageBus">
        <listeners>
          <add name="SignalR-Bus" />
        </listeners>
      </source>
     <source name="SignalR.ServiceBusMessageBus">
        <listeners>
            <add name="SignalR-Bus" />
        </listeners>
     </source>
     <source name="SignalR.ScaleoutMessageBus">
        <listeners>
            <add name="SignalR-Bus" />
        </listeners>
      </source>
      <source name="SignalR.Transports.WebSocketTransport">
        <listeners>
          <add name="SignalR-Transports" />
        </listeners>
      </source>
      <source name="SignalR.Transports.ServerSentEventsTransport">
          <listeners>
              <add name="SignalR-Transports" />
          </listeners>
      </source>
      <source name="SignalR.Transports.ForeverFrameTransport">
          <listeners>
              <add name="SignalR-Transports" />
          </listeners>
      </source>
      <source name="SignalR.Transports.LongPollingTransport">
        <listeners>
            <add name="SignalR-Transports" />
        </listeners>
      </source>
      <source name="SignalR.Transports.TransportHeartBeat">
        <listeners>
            <add name="SignalR-Transports" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="SignalRSwitch" value="Verbose" />
    </switches>
    <sharedListeners>
      <add name="SignalR-Transports" 
           type="System.Diagnostics.TextWriterTraceListener" 
           initializeData="transports.log.txt" />
        <add name="SignalR-Bus"
           type="System.Diagnostics.TextWriterTraceListener"
           initializeData="bus.log.txt" />
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
  </entityFramework>
</configuration>

Ao executar o aplicativo no Visual Studio, você pode exibir os logs na janela Saída .

Como chamar métodos de cliente e gerenciar grupos de fora da classe Hub

Para chamar métodos de cliente de uma classe diferente da classe Hub, obtenha uma referência ao objeto de contexto SignalR para o Hub e use-a para chamar métodos no cliente ou gerenciar grupos.

A classe de exemplo StockTicker a seguir obtém o objeto de contexto, armazena-o em uma instância da classe , armazena a instância de classe em uma propriedade estática e usa o contexto da instância de classe singleton para chamar o updateStockPrice método em clientes conectados a um Hub chamado StockTickerHub.

// For the complete example, go to 
// http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-aspnet-signalr
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
{
    // Singleton instance
    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
        () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));

    private IHubContext _context;

    private StockTicker(IHubContext context)
    {
        _context = context;
    }

    // This method is invoked by a Timer object.
    private void UpdateStockPrices(object state)
    {
        foreach (var stock in _stocks.Values)
        {
            if (TryUpdateStockPrice(stock))
            {
                _context.Clients.All.updateStockPrice(stock);
            }
        }
    }

Se você precisar usar o contexto várias vezes em um objeto de longa duração, obtenha a referência uma vez e salve-a em vez de obtê-la novamente a cada vez. Obter o contexto uma vez garante que o SignalR envie mensagens para clientes na mesma sequência em que os métodos hub fazem invocações de método de cliente. Para obter um tutorial que mostra como usar o contexto do SignalR para um Hub, consulte Transmissão de servidor com ASP.NET SignalR.

Chamando métodos de cliente

Você pode especificar quais clientes receberão o RPC, mas você tem menos opções do que quando chama de uma classe hub. O motivo para isso é que o contexto não está associado a uma chamada específica de um cliente, portanto, todos os métodos que exigem conhecimento da ID de conexão atual, como Clients.Others, ou Clients.Caller, Clients.OthersInGroupnão estão disponíveis. As seguintes opções estão disponíveis:

  • Todos os clientes conectados.

    context.Clients.All.addContosoChatMessageToPage(name, message);
    
  • Um cliente específico identificado pela ID de conexão.

    context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);
    
  • Todos os clientes conectados, exceto os clientes especificados, identificados pela ID de conexão.

    context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • Todos os clientes conectados em um grupo especificado.

    context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • Todos os clientes conectados em um grupo especificado, exceto clientes especificados, identificados pela ID de conexão.

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    

Se você estiver chamando sua classe não Hub de métodos em sua classe Hub, poderá passar a ID de conexão atual e usá-la com Clients.Client, Clients.AllExceptou Clients.Group para simular Clients.Caller, Clients.Othersou Clients.OthersInGroup. No exemplo a seguir, a MoveShapeHub classe passa a ID de conexão para a Broadcaster classe para que a Broadcaster classe possa simular Clients.Others.

// For the complete example, see
// http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20
// This sample only shows code that passes connection ID to the non-Hub class,
// in order to simulate Clients.Others.
public class MoveShapeHub : Hub
{
    // Code not shown puts a singleton instance of Broadcaster in this variable.
    private Broadcaster _broadcaster;

    public void UpdateModel(ShapeModel clientModel)
    {
        clientModel.LastUpdatedBy = Context.ConnectionId;
        // Update the shape model within our broadcaster
        _broadcaster.UpdateShape(clientModel);
    }
}

public class Broadcaster
{
    public Broadcaster()
    {
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    }

    public void UpdateShape(ShapeModel clientModel)
    {
        _model = clientModel;
        _modelUpdated = true;
    }

    // Called by a Timer object.
    public void BroadcastShape(object state)
    {
        if (_modelUpdated)
        {
            _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
            _modelUpdated = false;
        }
    }
}

Gerenciando a associação ao grupo

Para gerenciar grupos, você tem as mesmas opções que em uma classe hub.

  • Adicionar um cliente a um grupo

    context.Groups.Add(connectionID, groupName);
    
  • Remover um cliente de um grupo

    context.Groups.Remove(connectionID, groupName);
    

Como personalizar o pipeline de Hubs

O SignalR permite injetar seu próprio código no pipeline do Hub. O exemplo a seguir mostra um módulo de pipeline do Hub personalizado que registra cada chamada de método de entrada recebida do cliente e da chamada de método de saída invocada no cliente:

public class LoggingPipelineModule : HubPipelineModule 
{ 
    protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context) 
    { 
        Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name); 
        return base.OnBeforeIncoming(context); 
    }   
    protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context) 
    { 
        Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub); 
        return base.OnBeforeOutgoing(context); 
    } 
}

O código a seguir no arquivo Startup.cs registra o módulo a ser executado no pipeline do Hub:

public void Configuration(IAppBuilder app) 
{ 
    GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
    app.MapSignalR();
}

Há muitos métodos diferentes que você pode substituir. Para obter uma lista completa, consulte Métodos HubPipelineModule.