dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.53k stars 25.3k forks source link

SignalR send to users and groups in one call #22214

Open jhofer-arian opened 3 years ago

jhofer-arian commented 3 years ago

Is your feature request related to a problem? Please describe.

I am trying to send a SignalR Message to a List of Groups and Users. For example, all Admins and Managers should get this message as well as a certain user who triggered an API call in the program. To do this we are using the following code.

   [CapSubscribe(EventNames.SignalRUpdateEventName)]
        public async Task Handle(SignalREvent @event)
        {
            //Adds a message ID to each message to give the client the option to discard duplicates
            @event.JsonPayload.Add("MessageId", Guid.NewGuid().ToString());
            if (!@event.Groups.IsNullOrEmpty())
            {
                await _hubContext.Clients.Groups(@event.Groups).SendAsync(@event.MethodName, new object[] {@event.JsonPayload});
            }

            if (!@event.Users.IsNullOrEmpty())
            {
                await _hubContext.Clients.Users(@event.Users)
                    .SendAsync(@event.MethodName, new object[] {@event.JsonPayload});
            }

        }

This results in multiple Messages being sent by SignalR and our client receiving multiple messages if the user is, for example, the one who initiated the call and is part of the group Admin and Manager.

Describe the solution you'd like

My Solution would be that I could call something like this: _hubContext.Clients.Groups(@event.Groups).Users(@event.Users).SendAsync(@event.MethodName, new object[] {@event.JsonPayload}); And in the background singalR would aggregate all the connection Ids so that only one message per connection would be sent.

davidfowl commented 3 years ago

I don't think this is common enough to merge both groups and users, at least I haven't seen this request come up before.

And in the background singalR would aggregate all the connection Ids so that only one message per connection would be sent.

I think this also is assuming quite a bit about the implementation. Why does it need to be one message. How many messages do you end up sending today and why is that an issue in your scenario?

jhofer-arian commented 3 years ago

Hi David,

yeah i made some assumptions about the implemntation.

I'm receiving two messages in the client, one from this call await _hubContext.Clients.Groups(@event.Groups).SendAsync(@event.MethodName, new object[] {@event.JsonPayload}); and one from that await _hubContext.Clients.Users(@event.Users).SendAsync(@event.MethodName, new object[] {@event.JsonPayload});

I am aware that this is expected behaviour, because I sent two messages. But now I would love to know if the users from my users list were in the groups I sent to so I can prevent sending another message to them. The resulting issue is that my client receives two messages and prints them twice e.g. as two toast messages.

If this solution _hubContext.Clients.Groups(@event.Groups).Users(@event.Users).SendAsync(@event.MethodName, new object[] {@event.JsonPayload}); isn't an option it would be nice if we could figure out if an connection is already in a group. An example call would be: _hubContext.Groups.IsSubscribed("groupName", connectionId);

Thanks, Julian

BrennanConroy commented 3 years ago

Today we recommend that you either store all group/user information and query that yourself to avoid sending to the same connection twice, or structuring your messages such that clients can filter out identical messages if received.

I think we've seen similar issues filed for this type of feature, but can't find any right now.

IsSubscribed wouldn't completely prevent this type of issue as you would then need to loop over all connections manually and send to them if you found a single connection in both subsets.

We haven't been able to come up with a general case solution for this type of scenario that works with both single server and multiple servers (scaleout).

jhofer-arian commented 3 years ago

Thanks for the recommandation with implementing a seperate storage for groups. I guess I will do that that.

IsSubscribed would be a start to solving the problem, it is not ideal but I would assume that it could work on the scaleout version.

Ideal for me would be, if the call await _hubContext.Clients.Groups(@event.Groups).SendAsync(@event.MethodName, new object[] {@event.JsonPayload}); returned a list of connectionIds which the message has been sent to.

BrennanConroy commented 3 years ago

returned a list of connectionIds which the message has been sent to

This would be awful for perf if we did this.

I think we just need to update our documentation to talk about the limitations here and offer suggestions on how to do this.