Reputation: 271
I have a requirement to send notification to specific user. I am using dotnet core 3.1. ASPNETCORE signalR for the notification. I am able to send the messages to all clients but unablt to do so for specific user.
EDIT 1
My Hub looks like :
public class NotificationHub : Hub
{
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception ex)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnDisconnectedAsync(ex);
}
}
And i am calling the SendAsync method from Controller as :
private IHubContext<NotificationHub> _hub;
public NotificationController(IHubContext<NotificationHub> hub)
{
_hub = hub;
}
[HttpGet]
public IActionResult Get()
{
//_hub.Clients.All.SendAsync("SendMessage",
_hub.Clients.All.SendAsync("SendMessage",
new
{
val1 = getRandomString(),
val2 = getRandomString(),
val3 = getRandomString(),
val4 = getRandomString()
});
return Ok(new { Message = "Request Completed" });
}
Upvotes: 7
Views: 13317
Reputation: 60642
To send a message to a particular user you simply call:
_hubContext.Clients.User(useridentifier).SendAsync("my_message");
//_hubContext is your context, can be a DI injected variable etc
If you're using ASP.NET Core built-in authentication, it works out of the box as long as you have [Authorize]
in your Hub.
BUT! if you're using custom authentication, like CookieAuthetication
for example, then you have to supply a "name-provider" to SignalR, so it knows which user to put in which bucket.
Like this:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
//for example just return the user's username
return connection.User?.Identity?.Name;
}
}
And then register this "provider" in Startup.cs
//needed for SignalR to support "Context.UserIdentifier" and built-in user mapping
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
Upvotes: 1
Reputation: 1140
I think is a bug in ASP.NET Core SignalR (I'm also using ASP.NET Core 3.1),
_hubContext.Clients.User(userId).SendAsync(...)
doesn't seem to work. Note that by default userId
is what it's stored as ClaimTypes.NameIdentifier
for Claims based authentication (From doc : By default, SignalR uses the ClaimTypes.NameIdentifier from the ClaimsPrincipal associated with the connection as the user identifier.)
So I had to create my own connection manager and it works fine, once getting all user's connections you can loop and write something like
_hubContext.Clients.Client(connectionId).SendAsync(...)
Here is a hint for how user's connections are managed :
public class YourHub : Hub
...
public override Task OnConnectedAsync()
{
//Get userId
_connectionHubManager.AddConnection(userId, Context.ConnectionId);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
_connectionHubManager.RemoveConnection(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
To get userId
depends on your implentation.
If userId
is registred as ClaimTypes.Name
you can simply get it :
Context.User.Identity.Name;
If userId
is registred as ClaimTypes.NameIdentifier
you need to do :
(Context.User.Identity as ClaimsIdentity)?.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
Don't forget to add [Authorize]
to your hub class in order to get those fields filled and check Doc for SignalR authentication.
Upvotes: 0
Reputation: 3611
You can send notifications to a specified if you know the users connectionId or add the connected user to an group.
In the hub, assuming you know the connectionId:
await this.Clients.Client("connectionId").SendAsync("MethodName", "The message");
You also can add a specified user in to a group and then send the message to the group:
await this.Groups.AddToGroupAsync("connectionId", "groupName");
await this.Clients.Group("groupName").SendAsync("MethodName", "The message");
You can read more about it in this Microsoft Documentation.
Update:
To answer your updated question, you must provide the authorization attribute to your hub in order to have identity name and other parameters
[Authorize]
public class NotificationHub : Hub
{
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception ex)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, Context.User.Identity.Name);
await base.OnDisconnectedAsync(ex);
}
}
And then on your Angular client, you must provide a token to connect to your hub like:
private configureSignalR(token: string) {
this.hubMessageConnection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Error).withUrl(this.signalRUrl + "/notifications",
{
accessTokenFactory: () => token
})
.withAutomaticReconnect()
.build();
}
You can read more about Authentication and authorization in the microsoft documentation.
Upvotes: 1
Reputation: 1
You can send message to specific user via user id
In this example, we will try to get current user info and send some data back to that user using user id:
In the hub:
public async Task GetInfo()
{
var user = await _userManager.GetUserAsync(Context.User);
await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
}
In the client:
connection.on('msg', function (...data) {
console.log('data:', data); // ["27010e2f-a47f-4c4e-93af-b55fd95f48a5", "[email protected]"]
});
connection.start().then(function () {
connection.invoke('getinfo');
});
Note: Make sure you've already mapped the hub inside UseEndpoints
method:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<YourHubName>("/yourhubname");
});
Upvotes: 3