Nico Schertler
Nico Schertler

Reputation: 32607

Check if a group contains any client

Context

I have the following workflow to let the server query data from a connected client:

// Server-side code
async Task<string> RequestDataFromClient(string groupName)
{
    var responseId = GenerateUniqueId();

    // let the clients know that we want something
    await hubContext.Clients.Group(groupName).Request(responseId);

    try
    {
        return await WaitForIncomingCall(responseId);
    }
    catch(TimeoutException)
    {
        return GetDataFromFallbackMethod();
    }
}

The server first sends a Request to all clients in a given SignalR group. Then it waits for any client to call the Respond hub method with the given responseId using WaitForIncomingCall. WaitForIncomingCall has the following signature

Task<string> WaitForIncomingCall(responseId)

If no client calls the Respond hub method after a period of time, WaitForIncomingCall throws a TimeoutException. If such an exception occurs, the server will use a fallback method to retrieve the data (e.g., from a cache).

Problem

If no client exists in the given SignalR group, the server will wait until the timeout is exceeded until it starts the fallback method. This will produce a significant delay for the caller of RequestDataFromClient. I would rather directly invoke the fallback method if there is no client in the SignalR group. Hence, if there is no one who could potentially respond, don't even ask.

Question

How can I find out if a SignalR group is empty or if the Request call could potentially have reached a connected client? Manually tracking connections does not seem to be a good idea as soon as load balancing comes into play.

Upvotes: 3

Views: 2956

Answers (1)

Zhi Lv
Zhi Lv

Reputation: 21451

As far as I know, at present Asp.net core SignalR doesn't have the build-in method to check whether the group is empty or if there any clients exists in the group. And from the official document, we could also find that:

Group membership isn't preserved when a connection reconnects. The connection needs to rejoin the group when it's re-established. It's not possible to count the members of a group, since this information is not available if the application is scaled to multiple servers.

To check whether the group contains any connected clients, as a workaround, in the hub method, you could define a global variable to store the group name and the online count, or you could store the user information (user name, group name, online status etc.) in the database.

Then, in the OnConnectedAsync method, you could add the user to the group and calculate the online count or change the user's online status, in the OnDisconnectedAsync method, remove users from the group and change the online user count. Create a custom method to check/get the online user count.

code like this (in this sample code, I use the Login user name to create groups, you could change it based on your scenario):

[Authorize]
public class ChatHub : Hub
{
    private static Dictionary<string, int> onlineClientCounts = new Dictionary<string, int>();

    public override Task OnConnectedAsync()
    { 
        var IdentityName = Context.User.Identity.Name;
        Groups.AddToGroupAsync(Context.ConnectionId, IdentityName);

        int count = 0;
        if (onlineClientCounts.TryGetValue(IdentityName, out count))
            onlineClientCounts[IdentityName] = count + 1;//increment client number
        else
            onlineClientCounts.Add(IdentityName, 1);// add group and set its client number to 1


        return base.OnConnectedAsync();
    }
    public async Task SendMessage(string user, string message)
    { 
        
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    public async Task SendMessageToGroup(string sender, string groupname, string message)
    { 
        //check if group contains clients or not via the global variable or check database.

        await Clients.Group(groupname).SendAsync("ReceiveMessage", sender, message);
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        var IdentityName = Context.User.Identity.Name;
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, IdentityName);

        int count = 0;
        if (onlineClientCounts.TryGetValue(IdentityName, out count))
        {
            if (count == 1)//if group contains only 1client
                onlineClientCounts.Remove(IdentityName);
            else
                onlineClientCounts[IdentityName] = count - 1;
        } 

        await base.OnDisconnectedAsync(exception);
    }
}

Upvotes: 3

Related Questions