SignalR - Enviando uma mensagem para um usuário específico usando (IUserIdProvider) * NOVO 2.0.0 *

101

Na última versão do Asp.Net SignalR, foi adicionada uma nova forma de enviar uma mensagem para um determinado usuário, utilizando a interface "IUserIdProvider".

public interface IUserIdProvider
{
   string GetUserId(IRequest request);
}

public class MyHub : Hub
{
   public void Send(string userId, string message)
   {
      Clients.User(userId).send(message);
   }
}

Minha pergunta é: Como posso saber para quem estou enviando minha mensagem? A explicação desse novo método é muito superficial. E o projecto de declaração do SignalR 2.0.0 com este bug e não compila. Alguém implementou esse recurso?

Mais informações: http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider

Abraços.

Igor
fonte
1
Você precisa examinar a autenticação e autorização com SignalR. O UserId fará parte do provedor IPrincipal.
Gjohn

Respostas:

152

SignalR fornece ConnectionId para cada conexão. Para descobrir qual conexão pertence a quem (o usuário), precisamos criar um mapeamento entre a conexão e o usuário. Isso depende de como você identifica um usuário em seu aplicativo.

No SignalR 2.0, isso é feito usando o embutido IPrincipal.Identity.Name, que é o identificador do usuário conectado conforme definido durante a autenticação ASP.NET.

No entanto, pode ser necessário mapear a conexão com o usuário usando um identificador diferente em vez de usar o Identity.Name. Para este propósito, este novo provedor pode ser usado com sua implementação customizada para mapear o usuário com a conexão.

Exemplo de mapeamento de usuários SignalR para conexões usando IUserIdProvider

Vamos supor que nosso aplicativo use um userIdpara identificar cada usuário. Agora, precisamos enviar uma mensagem para um usuário específico. Temos userIde message, mas o SignalR também deve saber o mapeamento entre nosso userId e a conexão.

Para conseguir isso, primeiro precisamos criar uma nova classe que implemente IUserIdProvider:

public class CustomUserIdProvider : IUserIdProvider
{
     public string GetUserId(IRequest request)
    {
        // your logic to fetch a user identifier goes here.

        // for example:

        var userId = MyCustomUserClass.FindUserId(request.User.Identity.Name);
        return userId.ToString();
    }
}

A segunda etapa é dizer ao SignalR para usar nosso em CustomUserIdProvidervez da implementação padrão. Isso pode ser feito no Startup.cs ao inicializar a configuração do hub:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var idProvider = new CustomUserIdProvider();

        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);          

        // Any connection or hub wire up and configuration should go here
        app.MapSignalR();
    }
}

Agora, você pode enviar mensagem para um usuário específico usando o seu userIdconforme mencionado na documentação, como:

public class MyHub : Hub
{
   public void Send(string userId, string message)
   {
      Clients.User(userId).send(message);
   }
}

Espero que isto ajude.

Sumant
fonte
Oi amigo, desculpe pelo feedback tardio! Porém tentei acessar o Id que gera CustomUserIdProvider mas o método "OnConnected" não é o mesmo. Como faço para vincular a um usuário no banco de dados? Obrigado!
Igor
7
O que é MyCustomUserClass?
Danny Bullis
2
"MyCustomUserClass" pode ser sua classe de usuário personalizada que contém o método FindUserId. Isso é apenas por exemplo. Você pode ter qualquer método em qualquer classe que retorne seu UserId e use-o aqui.
Sumant
5
Obrigado @Sumant, meu problema acabou sendo que b / c eu estava em um projeto de API da Web onde implementei OAuth 2 com token de portador, tive que implementar lógica para passar o token de portador na string de consulta, pois não pode ser puxado os cabeçalhos nessa solicitação inicial de conexão de sinal. Não foi possível usar apenas request.User.Identity.Name
xinunix
1
@Sumant eu já resolvi isso. O problema foi que coloquei app.MapSignalR();global.asax antes da autenticação.
Sr. Robot
38

Aqui está um começo .. Aberto a sugestões / melhorias.

Servidor

public class ChatHub : Hub
{
    public void SendChatMessage(string who, string message)
    {
        string name = Context.User.Identity.Name;
        Clients.Group(name).addChatMessage(name, message);
        Clients.Group("[email protected]").addChatMessage(name, message);
    }

    public override Task OnConnected()
    {
        string name = Context.User.Identity.Name;
        Groups.Add(Context.ConnectionId, name);

        return base.OnConnected();
    }
}

JavaScript

(Observe como addChatMessagee sendChatMessagetambém são métodos no código do servidor acima)

    $(function () {
    // Declare a proxy to reference the hub.
    var chat = $.connection.chatHub;
    // Create a function that the hub can call to broadcast messages.
    chat.client.addChatMessage = function (who, message) {
        // Html encode display name and message.
        var encodedName = $('<div />').text(who).html();
        var encodedMsg = $('<div />').text(message).html();
        // Add the message to the page.
        $('#chat').append('<li><strong>' + encodedName
            + '</strong>:&nbsp;&nbsp;' + encodedMsg + '</li>');
    };

    // Start the connection.
    $.connection.hub.start().done(function () {
        $('#sendmessage').click(function () {
            // Call the Send method on the hub.
            chat.server.sendChatMessage($('#displayname').val(), $('#message').val());
            // Clear text box and reset focus for next comment.
            $('#message').val('').focus();
        });
    });
});

Testando insira a descrição da imagem aqui

O Homem Muffin
fonte
Oi amigo, desculpe pelo feedback tardio! Mas como posso evitar a perda de CONNECTIONID? Obrigado.
Igor
5
@lgao, não faço ideia.
The Muffin Man
por que essa linha foi necessária --- Clients.Group ("[email protected]"). addChatMessage (nome, mensagem); ??
Thomas
@Thomas Eu provavelmente o incluí por causa da demonstração. Deve haver outra maneira de transmitir para um grupo específico, pois isso foi codificado.
The Muffin Man
Esta solução simples resolveu meu problema de enviar uma mensagem para um usuário logado específico. É simples, rápido e fácil de entender. Eu votaria a favor dessa resposta várias vezes, se pudesse.
Rafael AMS
5

É assim que você usa SignarR para atingir um usuário específico (sem usar nenhum provedor):

 private static ConcurrentDictionary<string, string> clients = new ConcurrentDictionary<string, string>();

 public string Login(string username)
 {
     clients.TryAdd(Context.ConnectionId, username);            
     return username;
 }

// The variable 'contextIdClient' is equal to Context.ConnectionId of the user, 
// once logged in. You have to store that 'id' inside a dictionaty for example.  
Clients.Client(contextIdClient).send("Hello!");
Matteo Gariglio
fonte
2
como você está usando isso contextIdClientnão entendeu :(
Neo
2

Veja Testes SignalR para o recurso.

O teste "SendToUser" obtém automaticamente a identidade do usuário passada usando uma biblioteca de autenticação regular do owin.

O cenário é que você tem um usuário que se conectou a partir de vários dispositivos / navegadores e deseja enviar uma mensagem para todas as suas conexões ativas.

Gustavo Armenta
fonte
Obrigado cara! Mas a versão 2.0 do projeto não está compilando SignalR aqui. : (. Infelizmente não consigo acessar.
Igor
0

Tópico antigo, mas acabei de descobrir isso em uma amostra:

services.AddSignalR()
            .AddAzureSignalR(options =>
        {
            options.ClaimsProvider = context => new[]
            {
                new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"])
            };
        });
Greg Gum
fonte