SignalR - wysyłanie wiadomości do określonego użytkownika przy użyciu (IUserIdProvider) * NEW 2.0.0 *

101

W najnowszej wersji Asp.Net SignalR został dodany nowy sposób wysyłania wiadomości do określonego użytkownika, przy użyciu interfejsu „IUserIdProvider”.

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

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

Moje pytanie brzmi: Skąd mam wiedzieć, do kogo wysyłam wiadomość? Wyjaśnienie tej nowej metody jest bardzo powierzchowne. A wersja robocza instrukcji SignalR 2.0.0 z tym błędem i nie kompiluje się. Czy ktoś zaimplementował tę funkcję?

Więcej informacji: http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider

Uściski.

Igor
źródło
1
Musisz zajrzeć do uwierzytelniania i autoryzacji za pomocą sygnalizującego. Identyfikator użytkownika będzie częścią dostawcy IPrincipal.
Gjohn,

Odpowiedzi:

153

SignalR zapewnia ConnectionId dla każdego połączenia. Aby dowiedzieć się, które połączenie należy do kogo (użytkownika), musimy utworzyć mapowanie między połączeniem a użytkownikiem. Zależy to od tego, jak identyfikujesz użytkownika w swojej aplikacji.

W SignalR 2,0 odbywa się to przy użyciu wbudowanego IPrincipal.Identity.Name, czyli identyfikatora zalogowanego użytkownika ustawionego podczas uwierzytelniania ASP.NET.

Jednak może być konieczne zmapowanie połączenia z użytkownikiem przy użyciu innego identyfikatora zamiast używania Identity.Name. W tym celu ten nowy dostawca może być używany z niestandardową implementacją do mapowania użytkownika z połączeniem.

Przykład mapowania użytkowników sygnalizujących na połączenia przy użyciu IUserIdProvider

Załóżmy, że nasza aplikacja używa znaku userIddo identyfikacji każdego użytkownika. Teraz musimy wysłać wiadomość do konkretnego użytkownika. Mamy userIdi message, ale SignalR musi również znać mapowanie między naszym userId a połączeniem.

Aby to osiągnąć, najpierw musimy stworzyć nową klasę, która implementuje 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();
    }
}

Drugim krokiem jest poinformowanie sygnalizującego, aby używał naszej CustomUserIdProviderzamiast domyślnej implementacji. Można to zrobić w pliku Startup.cs podczas inicjowania konfiguracji koncentratora:

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

Teraz możesz wysłać wiadomość do określonego użytkownika, używając jego, userIdjak wspomniano w dokumentacji, na przykład:

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

Mam nadzieję że to pomoże.

Sumant
źródło
Cześć przyjacielu, przepraszam za spóźnioną opinię! Ale próbowałem uzyskać dostęp do identyfikatora, który generuje CustomUserIdProvider, ale metoda „OnConnected” to nie to samo. Jak mogę połączyć się z użytkownikiem w bazie danych? Dziękuję Ci!
Igor
7
Co to jest MyCustomUserClass?
Danny Bullis
2
„MyCustomUserClass” może być Twoją niestandardową klasą użytkownika, która zawiera metodę FindUserId. To tylko przykład. Możesz mieć dowolną metodę w dowolnej klasie, która zwraca identyfikator użytkownika i użyć jej tutaj.
Sumant
5
Dzięki @Sumant, mój problem zakończył się tym, że b / c byłem w projekcie interfejsu API sieci Web, w którym zaimplementowałem OAuth 2 z tokenem okaziciela Musiałem zaimplementować logikę, aby przekazać token okaziciela w ciągu zapytania, ponieważ nie można go pobrać nagłówki tego początkowego żądania połączenia sygnalizatora. Nie można po prostu użyć request.User.Identity.Name
xinunix.
1
@Sumant, już to rozwiązałem. Problem polegał na tym, że app.MapSignalR();przed uwierzytelnieniem wstawiłem global.asax.
Mr. Robot
38

Oto początek .. Otwartość na sugestie / ulepszenia.

serwer

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

(Zwróć uwagę, jak addChatMessagei sendChatMessagesą również metody w kodzie serwera powyżej)

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

Testowanie wprowadź opis obrazu tutaj

The Muffin Man
źródło
Cześć przyjacielu, przepraszam za spóźnioną opinię! Ale jak mogę zapobiec utracie CONNECTIONID? Dziękuję Ci.
Igor
5
@lgao Nie mam pojęcia.
The Muffin Man
dlaczego ta linia była wymagana --- Clients.Group ("[email protected]"). addChatMessage (nazwa, wiadomość); ??
Thomas
@Thomas Prawdopodobnie załączyłem to ze względu na demo. Musi istnieć inny sposób transmisji do określonej grupy, ponieważ został on zakodowany na stałe.
The Muffin Man
To proste rozwiązanie rozwiązało mój problem z wysłaniem wiadomości do określonego zalogowanego użytkownika. Jego proste, szybkie i łatwe do zrozumienia. Gdybym mógł, kilka razy zagłosowałbym za tą odpowiedzią.
Rafael AMS
5

Oto jak użyj SignarR, aby dotrzeć do konkretnego użytkownika (bez korzystania z żadnego dostawcy):

 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
źródło
2
jak tego używasz, contextIdClientnie rozumiem :(
Neo
2

Spójrz na testy sygnalizujące dla funkcji.

Test „SendToUser” automatycznie pobiera tożsamość użytkownika przekazaną przy użyciu zwykłej biblioteki uwierzytelniania owin.

Scenariusz jest taki, że masz użytkownika, który połączył się z wielu urządzeń / przeglądarek i chcesz przesłać wiadomość do wszystkich jego aktywnych połączeń.

Gustavo Armenta
źródło
Dzięki! Ale projekt w wersji 2,0 nie kompiluje tutaj sygnalizującego. : (. Niestety nie mam do niego dostępu.
Igor
0

Stary wątek, ale właśnie natrafiłem na to w próbce:

services.AddSignalR()
            .AddAzureSignalR(options =>
        {
            options.ClaimsProvider = context => new[]
            {
                new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"])
            };
        });
Greg Gum
źródło