Updated on
To send a SignalR message to one client, call Clients.Client(connectionId).SendAsync("methodName", data) from inside the hub. The connection id comes from Context.ConnectionId, which SignalR assigns when the client connects.
That works for a single connection. For everything a user has open at once, Clients.User(userId) addresses every connection belonging to that user, and Clients.Group(name) addresses a set of connections we manage ourselves.
We build on the project from our SignalR Angular Tutorial: Real-Time Charts in ASP.NET Core article, which implements a real-time chart with ASP.NET Core SignalR on the server and Angular on the client side. That article’s hub pushes messages to every connected client; here we modify the same project so the hub can answer just one of them.
How Do We Send a SignalR Message to a Specific Client?
Three properties on the hub’s Clients object cover it, and the right one depends on what “a specific client” means.
Clients.Client(connectionId) targets one connection. The id comes from Context.ConnectionId inside the hub, and SendAsync("methodName", data) invokes the named handler on that client alone.
Clients.User(userId) targets a person rather than a connection, and reaches every connection they have open: two browser tabs and a phone all receive it. SignalR identifies the user from the NameIdentifier claim on the connection’s ClaimsPrincipal, so this only works once the application authenticates and sets that claim.
Clients.Group(groupName) targets a set we define. Connections join and leave with Groups.AddToGroupAsync() and Groups.RemoveFromGroupAsync(), a connection can belong to several groups, and SignalR keeps the membership.
Groups are the general answer. Connection ids are ephemeral and user ids need authentication, but a group is ours to define and manage. Microsoft’s SignalR documentation calls groups “the recommended way to send to a connection or multiple connections because the groups are managed by the application”.
Each client connecting to a SignalR hub has a unique connection id. We can retrieve this using the Context.ConnectionId property of the hub context. Using this, we can send messages just to that particular client:
public async Task BroadcastToConnection(string data, string connectionId)
=> await Clients.Client(connectionId).SendAsync("broadcasttoclient", data);
By default, SignalR uses the ClaimTypes.NameIdentifier from the ClaimsPrincipal associated with the connection as the user identifier. We can send messages to a particular user using this value:Â
public async Task BroadcastToUser(string data, string userId)
=> await Clients.User(userId).SendAsync("broadcasttouser", data);
Remember that when we are sending messages to a user, they will be sent to all connections associated with that user and not just any particular connection. However, sending messages to individual users requires our application to authenticate users and set the NameIdentifier claim in ClaimsPrincipal. Only then can the connections be mapped to specific users. Our JWT authentication in ASP.NET Core Web API article shows one way to set that claim.
A SignalR group is a collection of connections associated with a name. We can send messages to all connections in a group using the group name. Groups are the recommended way to send messages to multiple connections as it is easy to manage the groups in our application based on the application’s logic. A connection can become a member of multiple groups. Connections can be added to or removed from groups via the AddToGroupAsync() and RemoveFromGroupAsync() methods respectively:
public async Task AddToGroup(string groupName)
=> await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
public async Task RemoveFromGroup(string groupName)
=> await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
Then, we can send messages to the group using the group name:
public async Task BroadcastToGroup(string groupName) => await Clients.Group(groupName)
.SendAsync("broadcasttogroup", $"{Context.ConnectionId} has joined the group {groupName}.");
Groups are a good choice when we want to implement notifications specific to particular user groups, roles, etc in our application.Â
How Do We Get a SignalR Client’s ConnectionId?
Inside a hub method, Context.ConnectionId is the id of the connection that invoked it. That is the only place SignalR hands it out.
Server-side code outside the hub (a controller, a background service) has no direct access to it. Reaching a specific connection from there means either recording the id when the connection is established, or addressing the client by user or group instead through IHubContext<THub>.
Client-side code has the same problem in reverse. The connection id is not exposed to the client by default, so the article’s approach is to add a hub method that returns Context.ConnectionId and call it once the connection starts.
The important property is that connection ids do not survive. A reconnect produces a new one, so a stored id becomes stale silently and messages sent to it go nowhere.
That is the argument for groups and user ids over stored connection ids in anything long-lived.
Pushing from outside the hub is its own topic, and we cover it in calling a SignalR hub from a controller.
Implementing Client-Specific Messages Using SignalR
In the previous section, we discussed how we can send messages to individual connections, users, and groups. Now let’s straight away implement client-specific messages in our SignalR app. We are going to do that by modifying the applications that we created in the linked article.
In that article, we implemented an ASP.NET Core SignalR server that uses a timer to send real-time data to all connected clients. Then, we implemented an Angular chart in the client app which consumes this data. On clicking the chart, we send a message from the client to the server, which in turn pushes a message to all connected clients.
Here, we are going to modify the last step in such a way that once we click the Angular chart on the client app, it additionally sends the connectionId to the server, which helps the server to identify this client. Then, the SignalR hub can send a message back to just this client.
To implement this, first, let’s modify the ChartHub class in our server-side project:
public class ChartHub : Hub
{
public async Task BroadcastChartDataToClient(List<ChartModel> data, string connectionId) =>
await Clients.Client(connectionId).SendAsync("broadcastchartdata", data);
public string GetConnectionId() => Context.ConnectionId;
}
We add a BroadcastChartDataToClient() method that takes connectionId as an additional parameter. This way, we can find the client using the connectionId and send a message just to that client. The name differs from the BroadcastChartData() method of the original project on purpose: SignalR does not support hub method overloads, and two methods sharing one name throw at MapHub time.
One thing is worth saying out loud about this signature. The hub takes the connection id from the client, so any client that guesses or captures an id can address the owner of that connection. For a chart demo that is fine, and as a general pattern it is not: Clients.Caller removes the parameter altogether when the target is whoever called the method, and accepting a connection id from a client is only safe when the server has some reason to trust it.
Additionally, we add a new GetConnectionId() method, which returns the connectionId of the client.
Next, let’s modify our Angular app. We need to change the SignalRService to get the connectionId and pass it while sending the message to the server:
export class SignalrService {
public data: ChartModel[];
public connectionId: string;
public broadcastedData: ChartModel[];
private hubConnection: signalR.HubConnection
public startConnection = () => {
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl('https://localhost:5001/chart')
.withAutomaticReconnect()
.build();
this.hubConnection
.start()
.then(() => console.log('Connection started'))
.then(() => this.getConnectionId())
.catch(err => console.log('Error while starting connection: ' + err))
}
...
private getConnectionId = () => {
this.hubConnection.invoke('getconnectionid')
.then((data) => {
console.log(data);
this.connectionId = data;
});
}
public broadcastChartData = () => {
const data = this.data.map(m => {
const temp = {
data: m.data,
label: m.label
}
return temp;
});
this.hubConnection.invoke('broadcastchartdatatoclient', data, this.connectionId)
.catch(err => console.error(err));
}
...
}
In the startConnection() method, we call the getConnectionId() method, which invokes our hub method to return the connectionId. Once we get this value, we can set it as a property of the class. Later, when we invoke the broadcastchartdatatoclient hub method, we pass the connectionId so that our SignalR hub can identify the client using it.
We use invoke() rather than send() throughout, and the difference is worth one sentence: invoke() waits for the hub method to complete and can return a value, while send() is fire-and-forget. Our getConnectionId() round trip only works because invoke() returns a value.
We also add withAutomaticReconnect() to the builder chain, and it matters more here than it usually would. A reconnect is a new connection with a new id, so the value we stored is dead the moment the client comes back. The onreconnected callback receives the new id, so assigning it there is the whole fix. Our SignalR automatic reconnect article covers the retry schedule and the rest of the reconnect callbacks.
One note on the hub address. withUrl('https://localhost:5001/chart') is a development URL written by hand; in anything deployed it comes from configuration rather than from the source file.
That’s it. We have implemented client-specific message sending in SignalR.
Testing
Now it’s time to test the changes that we have made. For that, we need to run both the server application and the client app. To see client-specific behavior in action, let’s run two instances of the client app. Once two client instances are up and running, let’s click the chart on any one instance:
We can see that the SignalR hub sends a message back to just that client instance. Remember that before implementing this change, when we clicked the chart on any client instance, the SignalR hub was used to send a message to all connected client instances.
Now that we have learned how to send messages to specific clients, we can create a lot of cool features using this technique.Â
Which Clients Property Should We Use in SignalR?
Start from the audience, not from the API.
For a reply to whoever just called the hub method, Clients.Caller is simpler and safer than passing a connection id around: SignalR already knows who called.
For a broadcast, Clients.All reaches everyone connected, and Clients.Others reaches everyone except the caller, which is what most “someone did something” notifications actually want.
For one person, Clients.User is almost always the right choice over Clients.Client, because a person is not a connection. Microsoft’s SignalR documentation notes that “A single user in SignalR can have multiple connections to an app.” Reserve Clients.Client for cases where the specific connection is genuinely the target: the tab that made the request, a device being paired.
For anything with a shape (a room, a document, a tenant, a role), use groups. They cost one call to join, they survive as long as we maintain them, and they turn “who should receive this” into a question we answer at join time rather than at send time.
| Property | Sends to | Use it for |
|---|---|---|
Clients.All | Every connected client | Broadcasts (a global notice, a shared tick) |
Clients.Caller | The connection that invoked this hub method | Acknowledging or answering a request |
Clients.Others | Everyone except the caller | "Someone else did something" notifications |
Clients.Client(id) | One connection | The case this article covers |
Clients.Clients(ids) | A specific list of connections | A hand-picked set |
Clients.User(userId) | Every connection belonging to one user | Reaching a person, not a tab |
Clients.Users(userIds) | Every connection of several users | A hand-picked set of people |
Clients.Group(name) | Every connection in a group | Rooms, channels, tenants |
Clients.Groups(names) | Several groups | Overlapping audiences |
Clients.GroupExcept(name, ids) | A group, minus named connections | A room excluding the sender |
Clients.OthersInGroup(name) | A group, minus the caller | The common chat-room case |
The same reasoning transfers to other front ends. Our real-time charts with Blazor WebAssembly and SignalR article builds the same chart against the same kind of hub.
Conclusion
We have learned the following topics in this article:
- The concept of connections, users, and groups in SignalR
- How to send messages from SignalR hub to specific connections, users, and groups
- How to modify our SignalR app to send client-specific messages
Tested with .NET 10.0.10.


why I can’t see the BroadcastChartData method when I type _hub. in controller?
That is not how it works. The _hub object has its own properties that you can use to send your data to a client: _hub.Clients.All.SendAsync (and others of course).
hello , how to send notification form asp.net site to all client in local Lan network .
You can see here: https://code-maze.com/netcore-signalr-angular-realtime-charts/ how to send notification from the server to all the clients using SignalR
Looks interesting
Thanks for the tutorial , I got a question tough :
this implementation will only allow broadcasting to connected users , what if I want to send a notification to an offline client and when this client connects he can see the notifications he missed while offline .
what is the best approach in your opinion , should I store offline notifications somewhere and do a GET at the login method , or is there a better way ?
Thank you
Hello Youssef. To be honest, here, I didn’t work with such a use case but your idea makes perfect sense.
i’ve been searching a comprehensive article like this one for almost a week!! this one is the besti,tnx alot code-maze
You are most welcome. Thanks for reading it.
this article is good but not complete.what happens if when timer triggers we want to send messege to specific client, in this scenario , the client doesn’t initiate the connection so there is no connectionid exist, the question is how I can assign a user to a connection.
what I mean appliation has it’s own user manager but how can I find which connetion is belongs to that user
I am not sure that I understand. How do you expect to send SignalR messages to a specific client if the client didn’t start the SignalR connection at all? In our main example https://code-maze.com/netcore-signalr-angular-realtime-charts/, we first initiate the SignalR connection and then hit the action on the WebAPI that starts the timer. So, the SignalR connection is already open. If it fails to open, you can apply automatic reconnect: https://code-maze.com/signalr-automatic-reconnect-option/
Again, I am not sure that I understood your use case.
imagin you have the notification service, notifcations are generated on server for specific user ,and then server wants to send this new notification to the client, so server knows which user it is but how it can find the connectionid?
I can imagine all of that, but again, your connection must be open in order to use SignalR to send your notifications to the client. So, if your server is already aware of the user, which means that the user is authenticated, why not start the signalR connection at the time user gets authenticated, and then just use it to send notifications?
my problem is how ? signalR server can send messege to specific connectionId, how server can map between user and connectionId?
server has all the connections in clients.All but how it can find which of them belongs to specific user if we don’t set it when connection stablished.
Imagin this senario:user A,B,C are connected to server and after a while server decides to send message to client C , but how server can find between all connections which one belongs to client C?
there should be a way on the server side when connection established that I can tell server ,this is connection for user C
and when another connection established from user B then I tell server this connection belongs to user B and now server has this mapping and can recognize between all connections that it has in clients.All which one belongs to clinet B or C
Let me try to explain it this way.
As you can see in this article, each client/user starts the signalR connection separately and that connectionId is generated on the server per user. This means that you have a different connection for each user/client that consumes your API. So, how you want to tie that connectionId to that specific user is a custom logic, maybe you want to add the connectionid to the table next to the user’s credentials and then use it from there or any other way, but once your client (a single user is a single client) has started the SignalR connection at that moment you have the connectionId for that client and you can relate it to the currently logged in user.
Ok , thanks a lot for clarifications
I went to your code repository at https://github.com/CodeMazeBlog/realtime-charts-signalr-angular/tree/signalR-automatic-reconnect
In there, I found that the branch seemed to be the same as the original article linked. When I downloaded the code, I couldn’t see a difference either.
Hello Peter.
I must say I am a bit confused. This article is about sending specific messages using signalR, but your link is pointing to the source code of the other article about implementing automatic reconnect with signalR. Anyhow, all the branches are different, I’ve just checked again.
You have the main branch for the main article SignalR charts and then two more branches, one for the automatic reconnect and one for specific messages. Again, I’ve just checked the SignalR service file from the client project, and that file is different for all three articles, so everything should be ok there.
Oh no, that’s all my fault. I’m sorry. 🙈
Loved it! Good Articles.
Great starting point for anyone.
Many thanks to the author.
Thanks a lot.
Excelente compa!.
Thanks a lot.
Thanks!!!
Thank you too for reading the article.