Unable to send to SignalR group - c#

I am trying to send a message to the group and I cannot get it working. I add the users to the group but the message is not sent, though using Clients.All works. Here is my setup.
Javascript called to connect to the hub, it fetches the users in the group and returns them as users in the chatroom and then sends a joinRoom to the server so I can add the user to the group and send a message back from the server to the client stating they have joined.
Javascript To Connect
$.connection.hub.start()
.done(function () {
chatHub.server.getConnectedUsers("MyChat") //return user list
.done(function (connectedUsers) {
ko.utils.arrayForEach(connectedUsers, function (item) {
users.contacts.push(new chatR.user(item.Username));
});
}).done(function () {
chatHub.server.joinRoom("MyChat", "My Room")
.done()
.fail(function(){ alert('failed to join group')}); //join the group
});
});
Server Side JoinRoom
public async Task JoinRoom(string room, string displayName)
{
// context variables
var name = Context.User.Identity.Name;
var connectionId = Context.ConnectionId;
// new group
var group = new SignalGroup(room, displayName, SignalGroupType.Chatroom);
// adding relation to storage
_manager.AddGroup(name, group); <-- adds to database
// anouncing the room was joined
Clients.Group(room).joinedRoom(name); //<-- This does not work
//Clients.All.joinedRoom(name); <-- This works
//Clients.OthersInGroup(room).joinedRoom(name);
// add group to SignalR
await Groups.Add(room, connectionId); // <-- why does this have to be last? when I move this before the _manager.AddGroup it never sends the client message?
}
So the Clients.Group(room).joinedRoom(name) does not work, i get no error message and the client never receives the message. Here is the client function.
Client Side JoinedRoom
chatHub.client.joinedRoom = function (name) {
var connectedUser = new chatR.user(name);
users.contacts.push(connectedUser);
chat.messages.push(new chatR.chatMessage("System", name + " joined.", new Date()));
};
For a "Bonus" here is my SendChatMessage method that too works when I send to ALL but not to a group.
public void SendChatMessage(string room, string message)
{
// context variables
var name = Context.User.Identity.Name;
var user = _manager.GetUser(name);
if (user.IsInGroup(room))
{
// tells clients to addChatMessage
//Clients.All.addChatMessage(name, message, DateTime.Now);
Clients.Group(room).addChatMessage(name, message, DateTime.Now);
}
}
So my main question is, Why am I not able to send to the groups? I am clearly adding them to the group and sending a message to the group?
Second question is, Why does the JoinRoom method have to have the add to group call very last or it seem it does not work at all even with All?
If you have any helpful links for this stuff that would be great as well, I have read all the MS documentation and several tutorials which leaves me even more baffled on why this is not working.
EDIT: I added a Failed check on the joinRoom call to see if I get anything back and it appears as if SignalR is failing to join the group itself. I am not sure how I would troubleshoot this though.

You got the order of parameters wrong. It should be
Groups.Add(connectionId, room);
Also, if you want to use the group for this user in the same method, you need to add the user to the group first and await the call. Otherwise it's not really necessary to await.

Related

Return new data after POST request Best Practice

TL;DR: What's best practice to update users list in Client App after a request changes the list?
Currently I am creating a full stack app and as of right now, it only has registration/log in and an admin section that adds roles to the app and manages the users.
In my React app, currently, I make a request to the API to add a new user as the admin or change an existing user's name etc. I perform the request, send some data (about the new user or what should be changed) and receive a new list with the users, which I set in the component's state that re-renders the page with the new data.
In the API controller, I receive that data, perform some checks and if all is ok, create/update the user and query the DB for the user list and return it to the client. All in the same controller method.
My supervisor told me that's a bad idea and should make sure a controller method should only do 1 thing (SOLID) and find a different way to do this. I want to make sure that after any operation interacting with the DB, the page gets updated with the changes. I've checked Get request after Post request best practice? and I didn't find my answer there.
Some code samples:
// calling the user creation method
export const createUser = (user, onSucceed, onFail) => {
axios.post("api/users/creation", {
"firstName": user.firstName,
"lastName": user.lastName,
"email": user.email,
"password": user.password,
"roles": user.role
})
.then((response) => {
// onSucceed simply performs a few minor
// tasks for antd Table component and sets the state
// with the users list
onSucceed(response);
})
.catch((exception) => {
// shows notification with the error and message of the error
onFail(exception);
})
}
My controller method:
async public Task<IActionResult> CreateUser(CreateUserModel userToCreate)
{
// some checks for incoming data and other logic
// TODO: Best practice for updating list
// queries the DB and returns the latest list of users
var userDbList = _userManager.Users.ToList();
var userReturnList = userDbList.Select(async user => new GetUsersModel
{
Name = $"{user.FirstName} {user.LastName}",
UserName = user.UserName,
Email = user.Email,
Roles = await _userManager.GetRolesAsync(user)
});
return Ok(new { Users = userReturnList, Message = AppResources.UserCreated });
}
There are two standard ways:
Optimistic way:
you assume that data which had sent to server would be created or updated certainly, so you update the array in UI immediately if after a while server send an exception you should rollback the updated array.
Pessimistic way:
you want to be sure that changes was saved in database, then show to user. so you should just fetch data after create or update data.

How to get email from identityref

I've looked at the UserHttpClient, ProfileHttpClient and GraphHttpClient.
I cannot figure out how I can retrieve the email address from any of those when I have an IdentityRef from a work item (the "AssignedTo" field).
Earlier I assumed that the uniquename field always where the email address, but that seems not to be the case for premise installations?
I finally figured it out.
The identityRef contains a field called Descriptor which corresponds to the "user descriptor" in the ProfileHttpClient (Rest api).
Thus, to get the email one have to do the following:
public static Task<string> GetEmailAddress(this VssConnection connection, SubjectDescriptor descriptor)
{
var client = connection.GetClient<GraphHttpClient>();
var user = await client.GetUserAsync(descriptor.ToString());
return user?.MailAddress;
}
// .. and in your code (where assignedTo is an IdentityRef).
var email = await connection.GetEmailAddress(assignedTo.Descriptor);
Update
This doesn't work on Azure DevOps Server as the Graph is not available on it. So the question remains.
(Leaves this as an answer for the cloud version)

Can't send message to specific user with SignalR

I can't make works the message sending to one specific user from the code behind. Clients.All works, Clients.AllExcept(userId) works, but not Client.User(userId).
My hub:
public class MessagingHub : Hub
{
public override Task OnConnected()
{
var signalRConnectionId = Context.ConnectionId;
// for testing purpose, I collect the userId from the VS Debug window
System.Diagnostics.Debug.WriteLine("OnConnected --> " + signalRConnectionId);
return base.OnConnected();
}
}
My controller to send message from code behind:
public void PostMessageToUser(string ConnectionId)
{
var mappingHub = GlobalHost.ConnectionManager.GetHubContext<MessagingHub>();
// doesn't works
mappingHub.Clients.User(ConnectionId).onMessageRecorded();
// doesn't works
mappingHub.Clients.Users(new List<string>() { ConnectionId }).onMessageRecorded();
// works
mappingHub.Clients.All.onMessageRecorded();
// works (?!)
mappingHub.Clients.AllExcept(ConnectionId).onMessageRecorded();
}
How my hub is initialized on the JS:
var con, hub;
function StartRealtimeMessaging()
{
con = $.hubConnection();
hub = con.createHubProxy('MessagingHub');
hub.on('onMessageRecorded', function () {
$(".MessageContainer").append("<div>I've received a message!!</div>");
});
con.start();
}
And finally how I send a(n empty) message to the hub:
function TestSendToUser(connectionId)
{
$.ajax({
url: '/Default/PostMessageToUser',
type: "POST",
data: { ConnectionId: connectionId},// contains the user I want to send the message to
});
}
So, it works perfectly with mappingHub.Clients.All.onMessageRecorded(); but not with mappingHub.Clients.User(ConnectionId).onMessageRecorded(); or mappingHub.Clients.Users(new List<string>() { ConnectionId}).onMessageRecorded();.
But interestingly, it works with mappingHub.Clients.AllExcept(ConnectionId).onMessageRecorded(); : All users connected receive the message except the given userid, which means the userid is good, and the user is well identified. So, why Clients.User(ConnectionId) doesn't works?
If you want to send a message to one particular connection and when you want to use the ConnectionId, make sure you use Clients.Client, and not Clients.User
Like this:
public void PostMessageToUser(string connectionId)
{
var mappingHub = GlobalHost.ConnectionManager.GetHubContext<MessagingHub>();
// Like this
mappingHub.Clients.Client(connectionId).onMessageRecorded();
// or this
mappingHub.Clients.Clients(new List<string>() { connectionId }).onMessageRecorded();
}
I had the same problem. I couldn't get .User(ConnectionId) to work.
I have just spent days trying to get SignalR to report progress on a long processing job to only the client who requested the job. That is, it isn't a chat app which most of the examples describe.
Any 'long processing progress reporting' examples I found only have a sim of the job in the hub. I have a controller doing real work and need to send messages from the controller, not the hub.
I used this answer https://stackoverflow.com/a/21222303/3251300. as a workaround for your stated problem but have included all the code snippets I use for the long processing job in case they are useful for anyone who stumbles on this answer.
The workaround has an elegance in that it uses the .Group() feature. By setting each groupID equal to the internal userID, messages can be sent using .Group(userID) without having to separately maintain a list of the userID/connectionID relationships outside SignalR.
There may be a way to maintain the relationships in SignalR without using the .Group() feature but I haven’t found it yet.
Pass the userID to the view using a hidden type which then makes it available to the js.
<input type="hidden" value="#ViewBag.UserID" id="userID" />
Then in the js hub script use the following to send the userID to the hub when the hub connection starts up.
$.connection.hub.start()
.done(function () {
var userID = document.getElementById('userID').value;
$.connection.myHub.server.announce(userID);
})
.fail(function () { alert("Hub failed to start.") });
The hub then has one statement which associates the userID and connectionID to the groupID, which is then the same string as the userID.
public class MyHub : Hub
{
public void Announce(string userID)
{
Groups.Add(Context.ConnectionId, userID);
}
}
To send messages from the controller (Again, not the hub in this case, the message is reporting progress to the client on a long processing request running in the controller) after setting the hub context, use .Group() and the internal userID.
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
string fileMessage = "Some message";
hubContext.Clients.Group(userID).hubMessage(fileMessage);
This is then displayed in the view using the js to place the message in a div
$.connection.myHub.client.hubMessage = function (message) {
$("#hubMessages").html(message);
}
'#hubMessages' refers to this div in the view. Examples use .append which makes the div grow each time you send a message, .HTML replaces whatever is in the div with the new message.
<div id="hubMessages"></div>
Anyone who comes to this answer and is trying to get going on MVC and SignalR, a big shout out to Caleb who has a great series of intro vids for SignalR https://youtu.be/kr8uHeNjOKw Anyone who finds this answer who is new to SignalR I recommend you spend an hour watching these.
I face same problem.
I change from:
Clients.User(connectionId).SendAsync(CallbackDefinition.DirectMessage, directMessageResult);
to:
Clients.Client(connectionId).SendAsync(CallbackDefinition.DirectMessage, directMessageResult);
And it work :D
Thank to: Matthieu Charbonnier

Can not send message to specific user By ConnectionId in SignalR

i can not send message to specific user by connectionId when I try to send all users like this: context.Clients.All.updateMessages(message) - this code is working.
hare is Hub code:
public void Send(string userToId, string userForId, string message)
{
//Get Recipent (userIdfor) connectionId
var signalrhelper = new HomeController();
string userForconnectionId = signalrhelper.GetConnecionIdByUserId(userForId);
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ChatHubs>();
string messageSenderConnId= signalrhelper.GetConnecionIdByUserId(userToId);
//Call Receiver
context.Clients.Client(userForconnectionId).updateMessages(message);
//Call Sender
context.Clients.Client(messageSenderConnId).updateMessages(message);
}
Hare is My View:
$(function() {
// Declare a proxy to reference the hub.
var notifications = $.connection.chatHubs;
// Create a function that the hub can call to broadcast messages.
notifications.client.updateMessages = function(data) {
if (window.location.href.indexOf("Messages/DetailMessage?userId") > -1) {
$('#timeline-messages').append('{0}'.Stringformat(data));
} else {
ReplaceUpdateTargetIdToReturnData("Messages/GetMessages", "#header_inbox_bar", "#systemMessage");
}
};
$.connection.hub.start().done(function() {
var myClientId = $.connection.hub.id;
GetConnectionIdToSignalR("Home", "SaveConnectionIdbyUserName", "userId", #Session["UserId"], "hubConnectionId", myClientId);
$('#sendMessageButton').click(function() {
if ($('#sendMessageFiled').val().length > 1) {
// Call the Send method on the hub.
notifications.server.send(#Session["UserId"], myClientId, $('#sendMessageButton').attr("title"), $('#sendMessageFiled').val());
// Clear text box and reset focus for next comment.
$('#sendMessageFiled').val('').focus();
} else {
$('#sendMessageFiled').focus();
}
});
}).fail(function (e) {
alert(e);
});
});
Can anybody Know what's happen ?
By user, I assume you mean an authenticated user? If that is the case, you have to map connections to users first. For instance, a user can have 2 or more signalr connections. So the first step is mapping users to connections, then you can send a message to the user and all his/her connected agents will receive it.
There are several ways to map connections to users, the guide is here: http://www.google.co.uk/url?q=http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections&sa=U&ei=Tjj-VJuPMsPBOZGGgYgH&ved=0CAsQFjAA&usg=AFQjCNFXoGJOm3mzenAJbz46TUq-Lx2bvA
Although this post is already old: I had a similar issue yesterday and it took me hours! I had the connectionIds but no client received a notification. Context.Clients.All.aMethod(...) worked fine, but Context.Clients.Client(theid).aMethod(...) did not work. I finally realized that I stored the connection-ids in the database as an uniqueIdentifier and MS SQL converted the uniqueIdentifier values to uppercase and therefore the connectionids were not valid any more. I converted the connectinIds to lowercase before publishing to my connected clients and then it worked...maybe you experience a similar problem. But your post with an invalid connectionid because of blanks helped my finding the problem.

How to implement SignalR to show email notifications to receivers in ASP.NET MVC 4 Application

I have a requirement to show email notification to receivers using SignalR. I am developing my application using MVC 4. I am a beginner and do not have much knowledge for SignalR. So far I have followed the posts below and tried to prepare a sample that can fulfill my requirement.
http://www.codeproject.com/Articles/732190/Real-Time-Web-Solution-for-Chat-by-MVC-SignalR-H
http://techbrij.com/realtime-post-comment-notifications-signalr-knockout
One of above is using knockout.js and other is not. I have followed both and am not able to meet my requirement.
Here is the code from SignalR Hub class:
public void SendEmail(Email email)
{
email.SentBy = WebSecurity.CurrentUserId;
using (NotifyEntities db = new NotifyEntities())
{
UsersContext uc = new UsersContext();
db.Emails.Add(email);
db.SaveChanges();
var ret = new
{
Message = post.Message,
SentBy = post.SentBy,
};
Clients.Caller.sendEmail(ret);
#region add to table which contain user id and email_post_id (I am sending the email to 2 users/receivers)
UsersMessage msgRel = new UsersMessage();
msgRel.EID = email.Id;
msgRel.UID = 2; //user 1
db.UsersMessages.Add(msgRel);
db.SaveChanges();
msgRel = new UsersMessage();
msgRel.EID = email.Id;
msgRel.UID = 5;//user 2
db.UsersMessages.Add(msgRel);
db.SaveChanges();
#endregion
var unread = (from ure in db.Emails.ToList()
join um in db.UsersMessages on ure.Id equals um.EID
where um.UID == WebSecurity.CurrentUserId && um.IsReaded == false
orderby ure.Id descending
select new
{
Message = pst.Message,
SentBy = pst.SentBy,
}).ToArray();
Clients.All.loadUnreadPosts(unread); //this returns unread emails of currently logged in user
Clients.All.loadPosts(ret); // this returns all emails of currently logged in user
}
}
When I do not use knockout.js then I am not able to show instant notification, it only appears on page refresh and it display to all users instead of the receivers.
When I use knockout.js the notification is showing instantly but the same issue exists that message/notification is displaying to all users.
Please help me with this. I don't know how to create group for receivers of particular email.
In order to send the notification to specific users only, you would want to use the Group functionality of SignalR. Here is some documentation on how to set it up: link
You would probably end up creating a group per user on user login or some similar action.
public async Task UserOnline(string emailAddress)
{
await Groups.Add(this.Context.ConnectionId, emailAddress);
}
Then you would want to use something like this in your signalr SendEmail hub class.
Clients.Group(emailAddress).loadUnreadPosts(unread);
Clients.Group(emailAddress).loadPosts(ret);
As a side note, I recommend going through the documentation at the link I provided. There should be no reason that you need to use knockout to get signalR to work appropriately.
EDIT
I have gotten something similar to the above working before (although I have lost that code); but I just came across this article that may actually provide a more elegant solution.

Categories

Resources