Reputation: 32607
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).
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.
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
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