3

I can't make works the message sending to one specific user from the code behind. Clients.All works, Clients.AllExcept(userId) works, but not Client.User(userId).

My hub:

public class MessagingHub : Hub
{
    public override Task OnConnected()
    {
        var signalRConnectionId = Context.ConnectionId;
        // for testing purpose, I collect the userId from the VS Debug window
        System.Diagnostics.Debug.WriteLine("OnConnected --> " + signalRConnectionId);
        return base.OnConnected();
    }
}

My controller to send message from code behind:

public void PostMessageToUser(string ConnectionId)
{
    var mappingHub = GlobalHost.ConnectionManager.GetHubContext<MessagingHub>();

    // doesn't works
    mappingHub.Clients.User(ConnectionId).onMessageRecorded();

    // doesn't works
    mappingHub.Clients.Users(new List<string>() { ConnectionId }).onMessageRecorded();

    // works
    mappingHub.Clients.All.onMessageRecorded();

    // works (?!)
    mappingHub.Clients.AllExcept(ConnectionId).onMessageRecorded();

}

How my hub is initialized on the JS:

var con, hub;
function StartRealtimeMessaging()
{
    con = $.hubConnection();
    hub = con.createHubProxy('MessagingHub');

    hub.on('onMessageRecorded', function () {
        $(".MessageContainer").append("<div>I've received a message!!</div>");
    });
    con.start();
}

And finally how I send a(n empty) message to the hub:

function TestSendToUser(connectionId)
{
    $.ajax({
        url: '/Default/PostMessageToUser',
        type: "POST",
        data: { ConnectionId: connectionId},// contains the user I want to send the message to
    });
}

So, it works perfectly with mappingHub.Clients.All.onMessageRecorded(); but not with mappingHub.Clients.User(ConnectionId).onMessageRecorded(); or mappingHub.Clients.Users(new List<string>() { ConnectionId}).onMessageRecorded();.

But interestingly, it works with mappingHub.Clients.AllExcept(ConnectionId).onMessageRecorded(); : All users connected receive the message except the given userid, which means the userid is good, and the user is well identified. So, why Clients.User(ConnectionId) doesn't works?

Matthieu Charbonnier
  • 2,794
  • 25
  • 33

3 Answers3

5

If you want to send a message to one particular connection and when you want to use the ConnectionId, make sure you use Clients.Client, and not Clients.User

Like this:

public void PostMessageToUser(string connectionId)
{
    var mappingHub = GlobalHost.ConnectionManager.GetHubContext<MessagingHub>();

    // Like this
    mappingHub.Clients.Client(connectionId).onMessageRecorded();

    // or this
    mappingHub.Clients.Clients(new List<string>() { connectionId }).onMessageRecorded();
}
Matthieu Charbonnier
  • 2,794
  • 25
  • 33
4

I had the same problem. I couldn't get .User(ConnectionId) to work.

I have just spent days trying to get SignalR to report progress on a long processing job to only the client who requested the job. That is, it isn't a chat app which most of the examples describe.

Any 'long processing progress reporting' examples I found only have a sim of the job in the hub. I have a controller doing real work and need to send messages from the controller, not the hub.

I used this answer https://stackoverflow.com/a/21222303/3251300. as a workaround for your stated problem but have included all the code snippets I use for the long processing job in case they are useful for anyone who stumbles on this answer.

The workaround has an elegance in that it uses the .Group() feature. By setting each groupID equal to the internal userID, messages can be sent using .Group(userID) without having to separately maintain a list of the userID/connectionID relationships outside SignalR.

There may be a way to maintain the relationships in SignalR without using the .Group() feature but I haven’t found it yet.

Pass the userID to the view using a hidden type which then makes it available to the js.

 <input type="hidden" value="@ViewBag.UserID" id="userID" />

Then in the js hub script use the following to send the userID to the hub when the hub connection starts up.

$.connection.hub.start()
  .done(function () {
    var userID = document.getElementById('userID').value;
    $.connection.myHub.server.announce(userID);
   })
  .fail(function () { alert("Hub failed to start.") });

The hub then has one statement which associates the userID and connectionID to the groupID, which is then the same string as the userID.

 public class MyHub : Hub
   {
       public void Announce(string userID)
       {
           Groups.Add(Context.ConnectionId, userID);
       }
   }

To send messages from the controller (Again, not the hub in this case, the message is reporting progress to the client on a long processing request running in the controller) after setting the hub context, use .Group() and the internal userID.

var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
string fileMessage = "Some message";
hubContext.Clients.Group(userID).hubMessage(fileMessage);

This is then displayed in the view using the js to place the message in a div

$.connection.myHub.client.hubMessage = function (message) {
   $("#hubMessages").html(message);
}

'#hubMessages' refers to this div in the view. Examples use .append which makes the div grow each time you send a message, .HTML replaces whatever is in the div with the new message.

 <div id="hubMessages"></div>

Anyone who comes to this answer and is trying to get going on MVC and SignalR, a big shout out to Caleb who has a great series of intro vids for SignalR https://youtu.be/kr8uHeNjOKw Anyone who finds this answer who is new to SignalR I recommend you spend an hour watching these.

Brian.S
  • 131
  • 12
  • +1 for the answer, but, well.. creating groups of only one user is a workaround, and not a real solution. I can't believe that the `Clients.User(srUserId)` doesn't works... – Matthieu Charbonnier Jul 22 '17 at 13:06
  • Well, I finally found the answer. See my answer. (hope it helps you too ;)) – Matthieu Charbonnier Jul 22 '17 at 19:08
  • You are right, I should have used ‘workaround’ – I’ve amended answer. And brilliant work in finding the real answer. The .Group() workaround has an elegance in that it makes SignalR manage the internal userID/ConnectionID relationships, meaning userID can be used in the message requests directly without having to maintain the relationships separately. As a testing workaround for that I had added connectionID to the user db table. I didn’t want to have to do that in the live db. – Brian.S Jul 23 '17 at 22:56
  • Ok I see. It's pretty nice. And when a user has several connections, it adds the connections to the same group. – Matthieu Charbonnier Jul 24 '17 at 08:41
  • That's right. It shouldn't be a problem in my app, but there may be a finite limit on number of groups. I am still testing so haven't used it yet live. – Brian.S Jul 25 '17 at 10:34
  • Isn't a big security breach to pass through javascript the `UserId`? Anyone could set any number in there and receive messages they should never see. – Matthieu Charbonnier Jul 29 '17 at 09:11
  • I guess you are correct, I didn't consider that. In my app it is very unlikely to be an issue as all they will see is a process progress %, and then only if they guess the id of a currently running process. But it is insecure if you are sending sensitive information. You could encrypt the userID before sending to the View, then decrypt in the hub where is it used, then check that the decrypted userID is valid. Even a simple encrypt would be fine, such as an AND with a random key string. I don't see how else to identify a single user to the hub. Any ideas please let me know. – Brian.S Jul 30 '17 at 10:07
  • We could use the Dictionary to maintain the userID/connectionID pairing, then use the answer you found - Clients.Client(ConnectionId). But still need to identify the user in the hub when the connections starts. Thats what I don't yet understand. – Brian.S Jul 30 '17 at 10:13
  • I have implemented encryption of the userID using this https://forums.asp.net/t/1985531.aspx?how+to+encrypt+and+decrypt+password+in+mvc+4 Took me 2 days to find! By using the functions provided I got it working in 5 minutes. I'll use these functions elsewhere so a good find. – Brian.S Aug 02 '17 at 04:01
0

I face same problem.

I change from:

Clients.User(connectionId).SendAsync(CallbackDefinition.DirectMessage, directMessageResult);

to:

Clients.Client(connectionId).SendAsync(CallbackDefinition.DirectMessage, directMessageResult);

And it work :D

Thank to: Matthieu Charbonnier