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

C#asp.net MvcSignalr

C# Problem Overview


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. Thanks in advance.

C# Solutions


Solution 1 - C#

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!

Solution 2 - C#

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

Solution 3 - C#

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.

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
QuestionClint LView Question on Stackoverflow
Solution 1 - C#N. Taylor MullenView Answer on Stackoverflow
Solution 2 - C#Matthew CView Answer on Stackoverflow
Solution 3 - C#JeffView Answer on Stackoverflow