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
Related
I have a WebSite integrated with SignalR. It functions well, and it has a button which sends popup notification to all clients who are online. It works well when I click on the button.
My API is in another project but in the same Solution. I want to send the above notification by calling from the API side. Basically, a mobile app will send a request to API and then API will send a notification to all online web clients.
Below code runs and not gives the notification nor any error.
Is this fundamentally correct? Appreciate your help
API code (at WebAPI project)
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
GMapChatHub sendmsg = new GMapChatHub();
sendmsg.sendHelpMessage(record_id.ToString());
return "Done";
}
C# code (at Web project)
namespace GMapChat
{
public class GMapChatHub : Hub
{
public void sendHelpMessage(string token)
{
var context = GlobalHost.ConnectionManager.GetHubContext<GMapChatHub>();
context.Clients.All.helpMessageReceived(token, "Test help message");
}
}
}
Home.aspx file (at Web project)
var chat = $.connection.gMapChatHub;
$(document).ready(function () {
chat.client.helpMessageReceived = function (token,msg) {
console.log("helpMessageReceived: " + msg);
$('#helpMessageBody').html(msg)
$('#helpModal').modal('toggle');
};
}
You can not call that hub directly. Firs you need to install the .net client for SignalR from nuget. Then you need to initialize it like this :
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
using (var hubConnection = new HubConnection("your local host address"))
{
IHubProxy proxy= hubConnection.CreateHubProxy("GMapChatHub");
await hubConnection.Start();
proxy.Invoke("sendHelpMessage",record_id.ToString()); // invoke server method
}
// return sth. IEnumerable<string>
}
And opening a new connection per request may not be good idea you may make it per session (if you use) or static or time fashioned.
I need to send an instant message from the server to the client after the user has submitted a form in a browser.
I followed the Microsoft steps here to set up a signalR connection, created a Hub class, signalr.js etc.
The problem is that I can only invoke a message to all clients, but I need to invoke the message to the specific caller who initiated the request (otherwise everyone will get the message).
This is my POST Action in the HomeController.cs:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
{
//Invoke signal to all clients sending a message to initSignal WORKS FINE
await _signalHubContext.Clients.All.SendAsync("initSignal", "This is a message from the server!");
//Invoke signal to specified client where signalRConnectionId = connection.id DOES NOT WORK
await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);
return RedirectToAction("Success", inputs);
}
my client javascript file:
//Create connection and start it
const connection = new signalR.HubConnectionBuilder()
.withUrl("/signalHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start().catch(err => console.error(err.toString()));
console.log("connectionID: " + connection.id);
$("#signalRconnectionId").attr("value", connection.id);
//Signal method invoked from server
connection.on("initSignal", (message) => {
console.log("We got signal! and the message is: " + message);
});
I have tried debugging the action method and I get correctly passed in the connectionId which is "0" (incrementing by 1 per connection)
So here's the final solution I came up with inspired by the answer from this thread
I got the connectionId by calling the Hub class from client and then from client calling the controller passing in the connectionId.
Hub class:
public class SignalHub : Hub
{
public string GetConnectionId()
{
return Context.ConnectionId;
}
}
client-side javascript code executed at startup:
connection.start().catch(err => console.error(err.toString())).then(function(){
connection.invoke('getConnectionId')
.then(function (connectionId) {
// Send the connectionId to controller
console.log("connectionID: " + connectionId);
$("#signalRconnectionId").attr("value", connectionId);
});
});
HomeController.cs:
public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
{
//Invoke signal to specified client WORKS NOW
await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);
return RedirectToAction("Success", inputs);
}
It works fine, but still feels a little like a roundtrip, it would have been easier if we didn't have to go through the hub class to make this happen. Maybe just having the connectionId from the client-side to begin with, but maybe there is a good reason for the design :)
According to Microsoft, you can not access to the ConnectionId and Caller from outside a hub
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1
When hub methods are called from outside of the Hub class, there's no
caller associated with the invocation. Therefore, there's no access to
the ConnectionId, Caller, and Others properties.
I have a request from client to Server using SignalR-2.2.1. After that request, Server will send the results back to the caller.
Here is my JavaScript code :
$(document).ready(function () {
var myHub = $.connection.signalHub;
myHub.client.receive = function (tmp) {
var obj = $.parseJSON(tmp);
//do something
};
$.connection.hub.start().done(function () {
$('#xxxxx').click(function () {
var form = $("form");
form.submit();
myHub.server.sendSomething();
});
}).fail(function (reason) {
//do something
});
});
And here is the code from my Server Side
public partial class SignalHub : Hub
{
public void SendSomething()
{
Clients.All.receive(MyJson);
}
}
When I use Clients.All function, it works perfectly fine. But what I want is the server only send the result back to the caller (the one that send the request).
So I change Clients.All.receive(MyJson) to Clients.Caller.receive(MyJson). After I change it, now the client doesnt update the content. Do I need to specify my receive function in client side that it would receive something different?
My guess is that since you are calling form.submit(); you are probably navigating away from the page or possibly reloading it, in that sense a new connection with a different "connectionId" is established between the new/reloaded page, thus the message intended for Client.Caller is lost since it is associated with the connection established on the page that you navigated away from or refreshed.
You might want to consider posting your form-data using AJAX instead of a full form submit.
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.
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.