1

In a SignalR application on .net core, I have a working piece of code similar to the following:

public Task SendPrivateMessage(string user, string message)
{
    var sender = Context.User.Claims.FirstOrDefault(cl => cl.Type == "username")?.Value;
    return Clients.User(user)
                  .SendAsync("ReceiveMessage", $"Message from {sender}: {message}");
}

This sends a message from the currently connected user, to the specified user.

Now I'd like to send an individual message to every user connected; something like the following concept:

public Task UpdateMessageStatus()
{
    foreach(client in Clients.Users)
    {
         var nrOfMessages = GetMessageCount();
         client.SendAsync($"You have {nrOfMessages} messages.");
    }
}

How can I achieve this; i.e. how can I iterate over connected users, and send individual messages to each of them?

Edit / clarification

As mentioned, I needed to send individual (i.e. specialized) messages to each connected user, so using Clients.All was not an option, as that can only be used to send the same message to all connected users.

I ended up doing something similar to what Mark C. posted in the accepted answer.

Kjartan
  • 18,591
  • 15
  • 71
  • 96
  • Can you explain what the problem is with the current version of `UpdateMessageStatus` please ? – Spotted Jul 17 '18 at 13:02
  • Currently there are no properties exposed to get the connected users or connection ids, but you can keep a list of users currently connected to the Hub with OnConnected and OnDisconnected events. More info [here](https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections#in-memory-storage) – Thangadurai Jul 17 '18 at 13:35

3 Answers3

3

If you're deadset on not using the Clients.All API that is given to you, you can keep a local cache of the connected users and use that as your list of connected users.

Something like this :

static HashSet<string> CurrentConnections = new HashSet<string>();

    public override Task OnConnected()
    {
        var id = Context.ConnectionId;
        CurrentConnections.Add(id);

        return base.OnConnected();
    }

    public Task SendAllClientsMessage()
    {
        foreach (var activeConnection in GetAllActiveConnections())
        {
            Clients.Client(activeConnection).SendMessageAsync("Hi");
        }
    }

    public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
    {
        var connection = CurrentConnections.FirstOrDefault(x => x == Context.ConnectionId);

        if (connection != null)
        {
            CurrentConnections.Remove(connection);
        }

        return base.OnDisconnected(stopCalled);
    }


    //return list of all active connections
    public List<string> GetAllActiveConnections()
    {
        return CurrentConnections.ToList();
    }
Mark C.
  • 6,332
  • 4
  • 35
  • 71
  • Just for clarification, because @Mark C. did not say it explicitely: ASP.NET (neither core nor pure) store a collection containing users for you. You have to maintain that on your own.(like shown above) – Ole Albers Jul 17 '18 at 13:33
  • Any idea how to apply the above approach, but with injected hubcontext? – Yahia Dec 13 '18 at 16:21
  • @Yahia You still need to store the connections in some form, and use that storage to pull the clients that you want to broadcast to. I dont think much changes here – Mark C. Dec 13 '18 at 17:52
  • You are right. I ended up defining `SendAllClientsMessage as static in the hub, and using it from the hubcontext – Yahia Dec 14 '18 at 08:45
  • You should be using a thread safe collection - https://stackoverflow.com/a/18923091/16940 - The regular HashSet WILL come and bite you someday! – Simon_Weaver Jun 18 '19 at 02:05
  • I agree, use a `ConcurrentBag` or whatever collection type that fits the requirements. – Mark C. Jun 18 '19 at 13:29
  • Or locking. This example is going to get someone in trouble - which is ultimately the fault of the person copying code, but others will pay the price. – xr280xr Mar 11 '22 at 15:16
2

You can use Clients.All to send message to your all clients.

return Clients.All.SendAsync("ReceiveMessage", $"Message from {sender}: {message}");

dcy
  • 35
  • 7
0

I ended up creating an managing my own list of connected users, similar to the answer provided by Mark C..

Just to expand on it slightly, this will allow me to do something like the following concept:

public async Task CheckAllMessages()
{
    foreach (var user in ConnectedUsers)
    {
       var nr = GetNrOfMessagesForUser(user);
       if(nr > 0)
       {
           await Clients.User(user)
                        .SendAsync("ReceiveMessage", 
                                   $"User ({user}) has {nr} new messages waiting.");
       }
    }
}
Kjartan
  • 18,591
  • 15
  • 71
  • 96