Search code examples
signalrsignalr-hub

SignalR chat members don't get existing array of users in chat


SignalR Chat - based on:

  • jQuery 3.4.1
  • SignalR 2.2.2
  • Asp.Net Framework Web Application

I'm trying to maintain a list of who is connected in the chat. It's a list of 'user' objects, the user object being the container to store 'name' and their assigned 'color' (red, blue, or green, etc color of text)

So the server model is an object with a List member:

public class GroupMemberModel
{
    [JsonProperty("name")]
    public string UserName { get; set; }

    [JsonProperty("color")]
    public string Color { get; set; }

    [JsonProperty("avatar")]
    public string AvatarImagePath { get; set; }
}

public class MessageModel
{
    [JsonProperty("message")]
    public string Message { get; set; }

    [JsonProperty("user")]
    public GroupMemberModel User { get; set; }

    [JsonProperty("group")]
    public List<GroupMemberModel> ChatGroup { get; set; }
}

Then when the user sends a message, the server checks if this new user is in the list, and adds it if not:

    public void Send(MessageModel clientModel)
    {
        if (!clientModel.ChatGroup.Contains(clientModel.User))
        {
            clientModel.ChatGroup.Add(clientModel.User);
            clientModel.ChatGroup.Sort();
        }
        // Call the broadcastMessage method to update clients.
        Clients.All.broadcastMessage(clientModel);
    }

My issue is that anytime a new user joins, their initial 'knowledge' of the ChatGroup list of GroupMemberModel users is empty. So when a new user joins, the of 'participants' is always replaced with the single user who just joined... And this also happens between users already in the chat - the last person who left a message is the only person in the 'Participants' list.. So the list isn't being maintained at all.

So do I need to serialize / de-serialize the List ChatGroup member of MessageModel each time the server serverHub 'send' function is hit?

I thought SignalR would manage the objects on the server somehow without hitting a storage mechanism? I guess they are unique to each server call and that's why they're empty for every user? Hmmm - maybe the list has to be static?


Solution

  • SignalR manages users internally in its own group structures. You can't access it directly but just through a few accessors they provide that won't do what you want.

    I did something very similar.

    For the List, I used a static dictionary. It has to be static or a Singleton since a new hub is created for every connection. A Hub is like a webpage where a new instance is created when the client connects and destroyed when they disconnect. Any variable local to the Hub is created and destroyed with it.

    static readonly ConcurrentDictionary<string, GroupMemberModel> Members; // string is the connection ID that is unique for every connection
    

    In the Hub, I added overloads for OnConnect, OnDisconnect, and OnReconnect that check to see if the user is present and add or remove the user from the Members list. Each method will have a Context object with the data for the connected client that you can use to add or remove members.

    For example:

    public override Task OnConnected()
    {
        AddNewMember(); // add member to list, here you can check any security, header, etc.
        // Note: Context contains the connected member
    
        return base.OnConnected();
    }
    
    public override Task OnDisconnected(bool stopCalled)
    {
        var member = GetMember();
        if (member != null)
        {
            Members.TryRemove(member.ConnectionId, out _);
        }
    
        return base.OnDisconnected(stopCalled);
    }
    
    public override Task OnReconnected()
    {
        var member = GetMember(); // find the connection in the list
        if (item == member)
        {
            var user = AddNewMember();
        }
    
        return base.OnReconnected();
    }
    

    This way, your server always maintains a list of connected members.