SignalR: envío de un mensaje a un usuario específico mediante (IUserIdProvider) * NUEVO 2.0.0 *

101

En la última versión de Asp.Net SignalR, se agregó una nueva forma de enviar un mensaje a un usuario específico, utilizando la interfaz "IUserIdProvider".

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

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

Mi pregunta es: ¿Cómo sé a quién le envío mi mensaje? La explicación de este nuevo método es muy superficial. Y el borrador de la Declaración de SignalR 2.0.0 con este error y no se compila. ¿Alguien ha implementado esta función?

Más información: http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider

Abrazos.

Igor
fuente
1
Debe examinar la autenticación y la autorización con SignalR. El UserId será parte del proveedor de IPrincipal.
Gjohn

Respuestas:

152

SignalR proporciona ConnectionId para cada conexión. Para encontrar qué conexión pertenece a quién (el usuario), necesitamos crear un mapeo entre la conexión y el usuario. Esto depende de cómo identifique a un usuario en su aplicación.

En SignalR 2.0, esto se hace mediante el uso incorporado IPrincipal.Identity.Name, que es el identificador de usuario que inició sesión según lo establecido durante la autenticación ASP.NET.

Sin embargo, es posible que deba asignar la conexión con el usuario utilizando un identificador diferente en lugar de utilizar Identity.Name. Para este propósito, este nuevo proveedor se puede utilizar con su implementación personalizada para mapear al usuario con la conexión.

Ejemplo de asignación de usuarios de SignalR a conexiones mediante IUserIdProvider

Supongamos que nuestra aplicación utiliza un userIdpara identificar a cada usuario. Ahora, necesitamos enviar un mensaje a un usuario específico. Tenemos userIdy message, pero SignalR también debe conocer el mapeo entre nuestro userId y la conexión.

Para lograr esto, primero necesitamos crear una nueva clase 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();
    }
}

El segundo paso es decirle a SignalR que use nuestra CustomUserIdProviderimplementación en lugar de la predeterminada. Esto se puede hacer en Startup.cs mientras se inicializa la configuración del concentrador:

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();
    }
}

Ahora, puede enviar un mensaje a un usuario específico usando su userId como se menciona en la documentación, como:

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

Espero que esto ayude.

Sumant
fuente
Hola amigo, ¡perdón por los comentarios tardíos! Pero intenté acceder a la identificación que genera CustomUserIdProvider pero el método "OnConnected" no es el mismo. ¿Cómo me vinculo a un usuario en la base de datos? ¡Gracias!
Igor
7
¿Qué es MyCustomUserClass?
Danny Bullis
2
"MyCustomUserClass" podría ser su clase de usuario personalizada que contiene el método FindUserId. Esto es solo por ejemplo. Podría tener cualquier método en cualquier clase que le devuelva UserId y usarlo aquí.
Sumant
5
Gracias @Sumant, mi problema terminó siendo que porque estaba en un proyecto de API web donde había implementado OAuth 2 con token de portador, tuve que implementar la lógica para pasar el token de portador en la cadena de consulta, ya que no se puede extraer de los encabezados de esa solicitud de conexión del señalizador inicial. No se pudo usar request.User.Identity.Name
xinunix
1
@Sumant Ya lo resolví. El problema fue que puse app.MapSignalR();global.asax antes de la autenticación.
Mr. Robot
38

Aquí es un comienzo ... Abierto a sugerencias / mejoras.

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 cómo addChatMessagey sendChatMessagetambién son métodos en el código del servidor anterior)

    $(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();
        });
    });
});

Pruebas ingrese la descripción de la imagen aquí

El hombre muffin
fuente
Hola amigo, ¡perdón por los comentarios tardíos! Pero, ¿cómo puedo evitar la pérdida de CONNECTIONID? Gracias.
Igor
5
@lgao No tengo ni idea.
The Muffin Man
por qué se requirió esta línea --- Clients.Group ("[email protected]"). addChatMessage (nombre, mensaje); ??
Thomas
@Thomas Probablemente lo incluí por el bien de la demostración. Tiene que haber otra forma de transmitir a un grupo específico, ya que estaba codificado.
The Muffin Man
Esta sencilla solución resolvió mi problema de enviar un mensaje a un usuario registrado específico. Es simple, rápido y fácil de entender. Votaría esta respuesta varias veces si pudiera.
Rafael AMS
5

Así es como se usa SignarR para dirigirse a un usuario específico (sin usar ningún proveedor):

 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
fuente
2
cómo estás usando esto no contextIdClientlo entendí :(
Neo
2

Mire las pruebas de SignalR para la función.

La prueba "SendToUser" toma automáticamente la identidad del usuario pasada mediante el uso de una biblioteca de autenticación owin regular.

El escenario es que tiene un usuario que se ha conectado desde múltiples dispositivos / navegadores y desea enviar un mensaje a todas sus conexiones activas.

Gustavo Armenta
fuente
¡Gracias hombre! Pero la versión 2.0 del proyecto no compila SignalR aquí. : (. Desafortunadamente no puedo acceder a él.
Igor
0

Hilo antiguo, pero me encontré con esto en una muestra:

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