I have a signalr hub with a function like this
public void SendSurveyNote(int surveyId,List<string> users){}
Here I want to add all users of the list into a group "Survey_" + surveyId Then sending the group a message. But I only have the user id but joining a group requires a connection id. So how could I manage that.
Also I wonder would it be a performance issue to send each user a message without a group?
I call the function above when I add a new Survey like this
private static HubConnection hubConnection = new HubConnection(ConfigurationManager.AppSettings["BaseUrl"]);
private static IHubProxy hubProxy = hubConnection.CreateHubProxy("myHub");
await hubConnection.Start();
hubProxy.Invoke("SendSurveyNote", model.Id, users);
thanks
You have access to the connection ID within Context. You'll want to establish groups within OnConnected. Observe the following implementation on your hub, where we will call it MyHub. We'll also group by Context.User.Identity.Name to establish a unique group per user, but this could be any value you wish to group by.
public class MyHub: Hub
{
public override Task OnConnected()
{
Groups.Add(Context.ConnectionId, Context.User.Identity.Name)
return base.OnConnected();
}
}
See Working with Groups in SignalR for more information
Related
I am building an ASP.NET Core application with Angular, and I am trying to implement a basic, one-on-one chat functionality using SignalR.
Since I want to allow one-on-one chatting and make chat messages persistent at some point, I'd like to be able to map User Id to SignalR Connection Id and send messages directly to a user based on their Id.
Now, all the examples I've seen use code within a Hub, which makes sense since Hub keeps track of Clients and their connection ids. But other logic that I'll have starts and ends inside my Controller, of course, and I can't call a hub directly from a controller.
Since SignalR is supposed to be relying on Identity by default, his is what I've tried so far:
[Route("send")]
[HttpPost]
public async Task<IActionResult> SendRequest([FromBody] Chat.Models.Message message)
{
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)); // I'd like to register this (current) userId with SignalR connection Id somehow
var sender = new ChatSender { Id= userId, Name = user.FullName };
await _hubContext.Clients.All.SendAsync("ReceiveMessage", sender, message.MessageText); // can't extend this, nor access hub directly
//var recipient = _hubContext.Clients.User(message.To); // message.To is a Guid of a recepient
//await recipient.SendAsync("ReceiveMessage", sender, message.MessageText);
return Ok();
}
The code above works as a broadcast, but if I replace the _hubContext.Clients.All.SendAsync with two commented out lines below, it does nothing.
Any suggestions?
SignalR mapping User Id to Connection Id
To map the user id /name with the connection id, you need to use the Hub's OnConnectedAsync method to get the userid/username and the connection id, then insert them into database. Code like below:
[Authorize]
public class ChatHub : Hub
{
private readonly ApplicationDbContext _context;
public ChatHub(ApplicationDbContext context)
{
_context = context;
}
public override async Task OnConnectedAsync()
{
//get the connection id
var connectionid = Context.ConnectionId;
//get the username or userid
var username = Context.User.Identity.Name;
var userId = Guid.Parse(Context.User.FindFirstValue(ClaimTypes.NameIdentifier));
//insert or updatde them into database.
var CId = _context.UserIdToCId.Find(userId);
CId.ConnectionId = connectionid;
_context.Update(CId);
await _context.SaveChangesAsync();
await base.OnConnectedAsync();
}
map User Id to SignalR Connection Id and send messages directly to a
user based on their Id.
You can pass your receiver id or name from your client to the SendRequest method, according to the receiver id or name to find the signalr connection id from database. After find the receiver's connection id, then use the following code to send message:
await _hubContext.Clients.Client("{receiver connection id}").SendAsync("ReceiveMessage", message);
the more code you can refer to:
public class HomeController : Controller
{
private readonly IHubContext<ChatHub> _hubContext;
private readonly ApplicationDbContext _context;
public HomeController( IHubContext<ChatHub> hubContext, ApplicationDbContext context)
{
_hubContext = hubContext;
_context = context;
}
...
/// <summary>
///
/// </summary>
/// <param name="receiver">receiver id or name</param>
/// <param name="message">message </param>
/// <returns></returns>
[Route("send")]
[HttpPost]
public async Task<IActionResult> SendRequest([FromBody] string receiver, Message message)
{
//1. according to the receiver id or name to find the signalr connection id
//To map the user id /name with the connection id, you need to use the Hub's OnConnectedAsync method to get the userid/username and the connection id.
//then insert them into database
//2. After find the receiver's connection id, then use the following code to send message.
await _hubContext.Clients.Client("{receiver connection id}").SendAsync("ReceiveMessage", message);
return Ok();
}
Note When the receiver is disconnected,remember to delete the receiver's connection id from database, avoid sending error.
Besides, you can refer to How can I make one to one chat system in Asp.Net.Core Mvc Signalr? to know more.
As the title says, how to validate if a connection id is still active using SignalR?
I have something similar as below where I map the connection ids to a user id. The problem is that in rare cases OnDisconnectedAsync does not triggeres.
Then I can't make the feature where the user is joining or leaving because it thinks that the user still have a connection.
I do have a "pinger" which run each 5 minutes that is updating a expire date but it is not reliable.
What I want is something like loop through all connection ids and verify if they are still active.
How can this be done? I thought maybe I can send a message to all connection ids for user X and see if I get something back and then do some kind of cleanup?
public class Chat : Hub
{
private IConnectionManager _manager;
public Chat(IConnectionManager manager)
{
_manager = manager;
}
public override Task OnConnectedAsync()
{
// Add connectionId and any other info you want to your connectionManager
_manager.Add(Context.ConnectionId, Context.User, Context.GetHttpContext());
}
public override Task OnDisconnectedAsync(Exception exception)
{
_manager.Remove(Context.ConnectionId);
}
}
SignalR has its own "pinger".
//
// Summary:
// Gets or sets the interval used by the server to send keep alive pings to connected
// clients. The default interval is 15 seconds.
public TimeSpan? KeepAliveInterval { get; set; }
And you can configure it on Startup like:
services.AddSignalR(hubOptions =>
{
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(hostConfiguration.SignalR.KeepAliveInterval);
}
So basically if the client will not respond in the defined timespan, it will trigger OnDisconnectedAsync.
I want to create a dashboard using signalr server broadcasting to a single client only, not all clients.
first you create group for each user
(user use multitab in browser and call back new connectionid)
https://learn.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-5.0
and
public class MyEndPoint : PersistentConnection
{
protected override IList<string> OnRejoiningGroups(IRequest request, IList<string> groups, string connectionId)
{
return groups;
}
}
Here we are using asp.net core singnalR alpha2 version. How to send notification for groups (group of users)? If any sample is there for scenario, post here the sample link.
In Hub write method with group.
public class SignalRCommonHub : Hub
{
public void SendToGroup(int groupId, int userId)
{
Clients.Group(groupId.ToString()).InvokeAsync("refresh", groupId, userId);
}
}
In controller called the hub method.
private readonly IHubContext<SignalRCommonHub> isignalRhub;
public SignalRModel(IHubContext<SignalRCommonHub> signalRhub)
{
this.isignalRhub = signalRhub;
}
public void RefreshPage(int groupId, int userId)
{
this.isignalRhub.Clients.Group(groupId.ToString()).InvokeAsync("refresh", groupId, userId);
}
In client side not trigger when call method in client side.
User online status update based on groups (group of users) using signalR sample available means post here link. Suggest idea for how to implement the user online status.
Thanks,
from your front end interface, you will need to setup the hub/connection, then make a call to Add/Remove groups using the connectionId and the group id/name.
eg: await _viewerHubContext.Groups.AddAsync(connectionId, groupName);
Then when you broadcast calls, you just use the group id/name and signalR will send to those groups.
eg: return Clients.Group(groupName).InvokeAsync("Send", "SendData", data);
I'm somewhat new to SignalR. I understand hubs to a limited degree, but I don't understand how two users can share a connection while excluding others.
My scenario is that I want an unauthenticated public website user to be able to initiate a private (not necessarily secure) chat session with a customer service user.
Is there an example or resource that my point me in the right direction?
I've looked at a few resources, including http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections but haven't found the right scenario.
You can create groups, so add some methods to your hub (a subscribe method should return a Task as they are asynchronous...)
public Task SubscribeToGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
Then you publish notifications to users of that group as normal but via the groups collection...
public void BroadcastMessageToGroup(string groupName, string message)
{
Clients.Group(groupName).onCaptionReceived(message);
}
Now only subscribers of that particular group will get the message!
Hope this helps.
You can find a tutorial here for SignalR Groups.
http://www.asp.net/signalr/overview/signalr-20/hubs-api/working-with-groups
You can create a group in Hub's API, in this method each user is a member of that group. And they send a message to that group ( via the server), and because they are only 2 members they are the only one's who see the messages ( privatly)
You can also message a group member directly by connection ID. This requires your app to keep track of connection IDs of users as they connect and disconnect, but this isn't too difficult:
//Users: stores connection ID and user name
public static ConcurrentDictionary Users = new ConcurrentDictionary();
public override System.Threading.Tasks.Task OnConnected()
{
//Add user to Users; user will supply their name later. Also give them the list of users already connected
Users.TryAdd(Context.ConnectionId, "New User");
SendUserList();
return base.OnConnected();
}
//Send everyone the list of users (don't send the userids to the clients)
public void SendUserList()
{
Clients.All.UpdateUserList(Users.Values);
}
//Clients will call this when their user name is known. The server will then update all the other clients
public void GiveUserName(string name)
{
Users.AddOrUpdate(Context.ConnectionId, name, (key, oldvalue) => name);
SendUserList();
}
//Let people know when you leave (not necessarily immediate if they just close the browser)
public override System.Threading.Tasks.Task OnDisconnected()
{
string user;
Users.TryRemove(Context.ConnectionId, out user);
SendUserList();
return base.OnDisconnected();
}
//Ok, now we can finally send to one user by username
public void SendToUser(string from, string to, string message)
{
//Send to every match in the dictionary, so users with multiple connections and the same name receive the message in all browsers
foreach(KeyValuePair user in Users)
{
if (user.Value.Equals(to))
{
Clients.Client(user.Key).sendMessage(from, message);
}
}
}