How to obtain connection ID of signalR client on the server side?
Asked Answered
S

4

58

I need to get the connection ID of a client. I know you can get it from the client side using $.connection.hub.id. What I need is to get in while in a web service I have which updates records in a database, in turn displaying the update on a web page. I am new to signalR and stackoverflow, so any advice would be appreciated. On my client web page I have this:

<script type="text/javascript">
    $(function () {
        // Declare a proxy to reference the hub. 
        var notify = $.connection.notificationHub;
        
        // Create a function that the hub can call to broadcast messages.
        notify.client.broadcastMessage = function (message) {
            var encodedMsg = $('<div />').text(message).html();// Html encode display message.
            $('#notificationMessageDisplay').append(encodedMsg);// Add the message to the page.
        };//end broadcastMessage
                 
        // Start the connection.
        $.connection.hub.start().done(function () {
            $('#btnUpdate').click(function () {
                //call showNotification method on hub
                notify.server.showNotification($.connection.hub.id, "TEST status");
            });
        });

        
    });//End Main function

    
</script>

everything works up until I want to update the page using signalR. The show notification function in my hub is this:

//hub function
public void showNotification(string connectionId, string newStatus){               
    IHubContext context = GlobalHost.ConnectionManager.GetHubContext<notificationHub>();
    string connection = "Your connection ID is : " + connectionId;//display for testing
    string statusUpdate = "The current status of your request is: " + newStatus;//to be displayed
    //for testing, you can display the connectionId in the broadcast message
    context.Clients.Client(connectionId).broadcastMessage(connection + " " + statusUpdate);
}//end show notification

how can I send the connectionid to my web service?

Hopefully I'm not trying to do something impossible.

Stotinka answered 3/1, 2014 at 16:47 Comment(6)
The second code snippet did not format for some reason. SorryStotinka
how do you know which client you want to update?Sadiras
also: why are you manually creating a hub instance if you're going to use GetHubContext?Sadiras
The hub instance I was creating to be honest was probably a bad decision. I now see from your first question the only way to go about doing this would be to have the client send their connectionid with the update. I edited the question with my client webpage code. How can I send this connectionid that way?Stotinka
in the case of your example code, you wouldn't have to send the id at all, since you could use Clients.Caller; you need to clarify the desired sequence of events - it's not clear what your requirements areSadiras
how do you mean I could use clients.caller?Stotinka
C
15

Taylor's answer works, however, it doesn't take into consideration a situation where a user has multiple web browser tabs opened and therefore has multiple different connection IDs.

To fix that, I created a Concurrent Dictionary where the dictionary key is a user name and the value for each key is a List of current connections for that given user.

public static ConcurrentDictionary<string, List<string>> ConnectedUsers = new ConcurrentDictionary<string, List<string>>();

On Connected - Adding a connection to the global cache dictionary:

public override Task OnConnected()
{
    Trace.TraceInformation("MapHub started. ID: {0}", Context.ConnectionId);
    
    var userName = "testUserName1"; // or get it from Context.User.Identity.Name;

    // Try to get a List of existing user connections from the cache
    List<string> existingUserConnectionIds;
    ConnectedUsers.TryGetValue(userName, out existingUserConnectionIds);

    // happens on the very first connection from the user
    if(existingUserConnectionIds == null)
    {
        existingUserConnectionIds = new List<string>();
    }

    // First add to a List of existing user connections (i.e. multiple web browser tabs)
    existingUserConnectionIds.Add(Context.ConnectionId);

    
    // Add to the global dictionary of connected users
    ConnectedUsers.TryAdd(userName, existingUserConnectionIds);

    return base.OnConnected();
}

On disconnecting (closing the tab) - Removing a connection from the global cache dictionary:

public override Task OnDisconnected(bool stopCalled)
{
    var userName = Context.User.Identity.Name;

    List<string> existingUserConnectionIds;
    ConnectedUsers.TryGetValue(userName, out existingUserConnectionIds);

    // remove the connection id from the List 
    existingUserConnectionIds.Remove(Context.ConnectionId);

    // If there are no connection ids in the List, delete the user from the global cache (ConnectedUsers).
    if(existingUserConnectionIds.Count == 0)
    {
        // if there are no connections for the user,
        // just delete the userName key from the ConnectedUsers concurent dictionary
        List<string> garbage; // to be collected by the Garbage Collector
        ConnectedUsers.TryRemove(userName, out garbage);
    }

    return base.OnDisconnected(stopCalled);
}
Carburize answered 8/8, 2019 at 9:23 Comment(1)
note that this solution is not completely thread safe, it is only thread safe when different user connect or disconnect, but if one user connects in different tabs at the same time, existingUserConnectionIds Add/Remove will be in race condition. For complete solution you should also lock the existingUserConnectionIds List.Cowskin
M
82

When a client invokes a function on the server side you can retrieve their connection ID via Context.ConnectionId. Now, if you'd like to access that connection Id via a mechanism outside of a hub, you could:

  1. Just have the Hub invoke your external method passing in the connection id.
  2. Manage a list of connected clients aka like public static ConcurrentDictionary<string, MyUserType>... by adding to the dictionary in OnConnected and removing from it in OnDisconnected. Once you have your list of users you can then query it via your external mechanism.

Ex 1:

public class MyHub : Hub
{
    public void AHubMethod(string message)
    {
        MyExternalSingleton.InvokeAMethod(Context.ConnectionId); // Send the current clients connection id to your external service
    }
}

Ex 2:

public class MyHub : Hub
{
    public static ConcurrentDictionary<string, MyUserType> MyUsers = new ConcurrentDictionary<string, MyUserType>();

    public override Task OnConnected()
    {
        MyUsers.TryAdd(Context.ConnectionId, new MyUserType() { ConnectionId = Context.ConnectionId });
        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        MyUserType garbage;

        MyUsers.TryRemove(Context.ConnectionId, out garbage);

        return base.OnDisconnected(stopCalled);
    }

    public void PushData(){
        //Values is copy-on-read but Clients.Clients expects IList, hence ToList()
        Clients.Clients(MyUsers.Keys.ToList()).ClientBoundEvent(data);
    }
}

public class MyUserType
{
    public string ConnectionId { get; set; }
    // Can have whatever you want here
}

// Your external procedure then has access to all users via MyHub.MyUsers

Hope this helps!

Mcintyre answered 3/1, 2014 at 18:42 Comment(7)
What about reconnects?Conventual
No need to add or remove a client on a reconnect.Mcintyre
Thanks, your example is very concise. I added how to send messages to all ID's in the dictionary as it took me a while to work out how to do it thread safely, which is not that obvious for learners, such as my self. +1Bosk
Remember: public override Task OnReconnected() { return base.OnConnected(); }Raillery
Hi @N.TaylorMullen I'm just bashing my heads against a scenario where we may have a million+ concurrent users and for that, we may be using Azure Signal R that will be connected to multiple servers providing concurrent connections. now we may want to broadcast the event according to the user type as stored in the Dictionary but how will Azure Signal R service know that which server has the key value pair for the passed user?Sulphate
What's PushData doing in above code? When would it be called?Whiteness
See comment by @PiotrKula, PushData is a way to broadcast to all users currently in the dictionary.Vendace
C
15

Taylor's answer works, however, it doesn't take into consideration a situation where a user has multiple web browser tabs opened and therefore has multiple different connection IDs.

To fix that, I created a Concurrent Dictionary where the dictionary key is a user name and the value for each key is a List of current connections for that given user.

public static ConcurrentDictionary<string, List<string>> ConnectedUsers = new ConcurrentDictionary<string, List<string>>();

On Connected - Adding a connection to the global cache dictionary:

public override Task OnConnected()
{
    Trace.TraceInformation("MapHub started. ID: {0}", Context.ConnectionId);
    
    var userName = "testUserName1"; // or get it from Context.User.Identity.Name;

    // Try to get a List of existing user connections from the cache
    List<string> existingUserConnectionIds;
    ConnectedUsers.TryGetValue(userName, out existingUserConnectionIds);

    // happens on the very first connection from the user
    if(existingUserConnectionIds == null)
    {
        existingUserConnectionIds = new List<string>();
    }

    // First add to a List of existing user connections (i.e. multiple web browser tabs)
    existingUserConnectionIds.Add(Context.ConnectionId);

    
    // Add to the global dictionary of connected users
    ConnectedUsers.TryAdd(userName, existingUserConnectionIds);

    return base.OnConnected();
}

On disconnecting (closing the tab) - Removing a connection from the global cache dictionary:

public override Task OnDisconnected(bool stopCalled)
{
    var userName = Context.User.Identity.Name;

    List<string> existingUserConnectionIds;
    ConnectedUsers.TryGetValue(userName, out existingUserConnectionIds);

    // remove the connection id from the List 
    existingUserConnectionIds.Remove(Context.ConnectionId);

    // If there are no connection ids in the List, delete the user from the global cache (ConnectedUsers).
    if(existingUserConnectionIds.Count == 0)
    {
        // if there are no connections for the user,
        // just delete the userName key from the ConnectedUsers concurent dictionary
        List<string> garbage; // to be collected by the Garbage Collector
        ConnectedUsers.TryRemove(userName, out garbage);
    }

    return base.OnDisconnected(stopCalled);
}
Carburize answered 8/8, 2019 at 9:23 Comment(1)
note that this solution is not completely thread safe, it is only thread safe when different user connect or disconnect, but if one user connects in different tabs at the same time, existingUserConnectionIds Add/Remove will be in race condition. For complete solution you should also lock the existingUserConnectionIds List.Cowskin
P
6

I beg to differ on the reconnect. The client remains in the list but the connectid will change. I do an update to the static list on reconnects to resolve this.

Providence answered 24/12, 2014 at 12:8 Comment(1)
How do you do that?Extravert
C
4

As Matthew C is not completely thread safe in situation of one user request multiple connection at same time, I used this code:

public static Dictionary<string, List<string>> ConnectedUsers = new ();

public override Task OnConnected()
{
        var connectionId = Context.ConnectionId;
        var userId = Context.User.Identity.Name; // any desired user id

        lock(ConnectedUsers)
        {
            if (!ConnectedUsers.ContainsKey(userId))
                ConnectedUsers[userId] = new();
            ConnectedUsers[userId].Add(connectionId);
        }  
}

public override Task OnDisconnected(bool stopCalled)
{
        var connectionId = Context.ConnectionId;
        var userId = Context.User.Identity.Name; // any desired user id

        lock (ConnectedUsers)
        {
            if (ConnectedUsers.ContainsKey(userId))
            {
                ConnectedUsers[userId].Remove(connectionId);
                if (ConnectedUsers[userId].Count == 0)
                    ConnectedUsers.Remove(userId);
            }
        }
}
Cowskin answered 21/9, 2022 at 10:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.