SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*

asp.netasp.net MvcReal TimeSignalr

asp.net Problem Overview


In the latest version of Asp.Net SignalR, was added a new way of sending a message to a specific user, using the 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);
   }
}

My question is: How do I know to whom I am sending my message? The explanation of this new method is very superficial. And the draft Statement of SignalR 2.0.0 with this bug and does not compile. Has anyone implemented this feature?

More Info : http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider

Hugs.

asp.net Solutions


Solution 1 - asp.net

SignalR provides ConnectionId for each connection. To find which connection belongs to whom (the user), we need to create a mapping between the connection and the user. This depends on how you identify a user in your application.

In SignalR 2.0, this is done by using the inbuilt IPrincipal.Identity.Name, which is the logged in user identifier as set during the ASP.NET authentication.

However, you may need to map the connection with the user using a different identifier instead of using the Identity.Name. For this purpose this new provider can be used with your custom implementation for mapping user with the connection.

Example of Mapping SignalR Users to Connections using IUserIdProvider

Lets assume our application uses a userId to identify each user. Now, we need to send message to a specific user. We have userId and message, but SignalR must also know the mapping between our userId and the connection.

To achieve this, first we need to create a new class which implements 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();
    }
}

The second step is to tell SignalR to use our CustomUserIdProvider instead of the default implementation. This can be done in the Startup.cs while initializing the hub configuration:

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

Now, you can send message to a specific user using his userId as mentioned in the documentation, like:

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

Hope this helps.

Solution 2 - asp.net

Here's a start.. Open to suggestions/improvements.

Server

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

(Notice how addChatMessage and sendChatMessage are also methods in the server code above)

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

Testing enter image description here

Solution 3 - asp.net

This is how use SignarR in order to target a specific user (without using any provider):

 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!");

Solution 4 - asp.net

Look at SignalR Tests for the feature.

Test "SendToUser" takes automatically the user identity passed by using a regular owin authentication library.

The scenario is you have a user who has connected from multiple devices/browsers and you want to push a message to all his active connections.

Solution 5 - asp.net

Old thread, but just came across this in a sample:

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

Solution 6 - asp.net

For anyone trying to do this in asp.net core. You can use claims.

public class CustomEmailProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
    }
}

Any identifier can be used, but it must be unique. If you use a name identifier for example, it means if there are multiple users with the same name as the recipient, the message would be delivered to them as well. I have chosen email because it is unique to every user.

Then register the service in the startup class.

services.AddSingleton<IUserIdProvider, CustomEmailProvider>();

Next. Add the claims during user registration.

var result = await _userManager.CreateAsync(user, Model.Password);
if (result.Succeeded)
{
    await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Model.Email));
}

To send message to the specific user.

public class ChatHub : Hub
{
    public async Task SendMessage(string receiver, string message)
    {
        await Clients.User(receiver).SendAsync("ReceiveMessage", message);
    }
}

Note: The message sender won't be notified the message is sent. If you want a notification on the sender's end. Change the SendMessage method to this.

public async Task SendMessage(string sender, string receiver, string message)
{
    await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", message);
}

These steps are only necessary if you need to change the default identifier. Otherwise, skip to the last step where you can simply send messages by passing userIds or connectionIds to SendMessage. For more

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIgorView Question on Stackoverflow
Solution 1 - asp.netSumantView Answer on Stackoverflow
Solution 2 - asp.netThe Muffin ManView Answer on Stackoverflow
Solution 3 - asp.netMatteo GariglioView Answer on Stackoverflow
Solution 4 - asp.netGustavo ArmentaView Answer on Stackoverflow
Solution 5 - asp.netGreg GumView Answer on Stackoverflow
Solution 6 - asp.netQudusView Answer on Stackoverflow