Public method not firing in SignalR - signalr

I have a simple application, like a chat, integrated with SignalR. I added a new method on my Hub and a new function on client side, like you can see below.
The problem is, my method called SendMessageChat isn't firing, because occurs the following error
TypeError: chat2.server.SendMessageChat is not a function
but the method chat2.server.send works fine, and I don't know why my second method doesn't work. Can someone help me ?
JavaScript
$(function () {
var chat2 = $.connection.redirectTask;
chat2.client.broadcastMessage = function (name, message) {
// Do something here
};
chat2.client.sendMessage = function (name, message) {
// Do something here
};
//$.connection.hub.logging = true;
$.connection.hub.start().done(function () {
/* BUTTON CLICK IN ANOTHER PAGE */
$('#btnFinish').click(function () {
chat2.server.send($.cookie("User"), $("#lista :selected").text());
});
/* CASE HIT ENTER INSIDE THE TEXT FIELD IN CHAT */
$(document).on("keypress", "#txtChat", function (e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) {
var message = $(this).val();
$(this).val("");
chat2.server.SendMessageChat($.cookie("User"), message);
}
});
});
});
SERVER SIDE
public class RedirectTask : Hub
{
public void Send(string nome, string message)
{
Clients.All.broadcastMessage(name, message);
}
public void SendMessageChat(string nome, string message)
{
Clients.All.sendMessage(name, message);
}
}

Reference
Need to change to
chat2.server.sendMessageChat($.cookie("User"), message);
Camel-casing of method names in JavaScript clients
By default, JavaScript clients refer to Hub methods by using a camel-cased version of the method name. SignalR automatically makes this change so that JavaScript code can conform to JavaScript conventions.
Server
public void NewContosoChatMessage(string userName, string message)
JavaScript client using generated proxy
contosoChatHubProxy.server.newContosoChatMessage(userName, message);
If you want to specify a different name for clients to use, add the HubMethodName attribute.
Server
[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)
JavaScript client using generated proxy
contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

Related

Asp.Net SignalR second tab does not fire onConnected event

I just created a sample project with signalR. I am just trying to test managing multiple connection. Everything works as expected when I open the first browser and load the page. It is going to fire the OnConnected event on the hub. But when I open another browser or different tab and load the page, it doesn't fire OnConnected event anymore. It shows $.connection.hub.id though.
Here is the hub
[HubName("genie")]
public class Genie : Microsoft.AspNet.SignalR.Hub
{
private static ConnectionManager _manager = new ConnectionManager();
[HubMethodName("AdminCommand")]
public void AdminCommand(string command, string message = "")
{
var connetions = _manager.GetConnections();
connetions.Remove(Context.ConnectionId);
Clients.Clients(connetions).onAdminCommand(command, message);
}
public override Task OnConnected()
{
_manager.AddConnection(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
_manager.RemoveConnection(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
}
And here is the javascript code:
var proxy = $.connection.genie;
$.connection.hub.start()
.done(function (state) {
console.log($.connection.hub.id);
});
proxy.on('onAdminCommand', function (command, message) {
if (command == "HappyGenie") {
$scope.goTo("happy/");
} else if (command == "SadGenie") {
$scope.goTo("sad/");
} else if (command == "CustomAnnouncement") {
dataService.setDataByKey("Announcement", message);
$scope.goTo("customannouncement/");
}
});
I establish a connection with the generated proxy.
Is there something I am doing wrong?
Thanks

Can't call javascript client method in signalr

The following code works fine in IIS Express, but failed in IIS10.
The weird thing is serverside method can successfully be invoked, however clientside method can't.
JavaScript
var hub = $.connection.liveRoomHub;
hub.client.addMessageToPage = function(data){
debugger;//here, this method never gets invoked
console.log(JSON.stringify(data));
};
$.connection.hub.start()
.done(function() {
hub.server.join('room1')
.done(function(){
debugger; //code can run into here
hub.server.sendMessage('user','test','room1');
})
});
C#
public class LiveRoomHub : Microsoft.AspNet.SignalR.Hub
{
public ILogger Logger { get; set; }
public async Task SendMessage(string name, string message, string roomName)
{
await Clients.Group(roomName)
.addMessageToPage(new
{
Name = name,
Message = message
});
Logger.Info($"{name}send msg:{message}in room:{roomName},");//logged
}
public async Task Join(string roomName)
{
await Groups.Add(Context.ConnectionId, roomName);
Logger.Info($"{Context.ConnectionId} enter room: {roomName}");//logged
}
}
All right, problem solved.
I'm using aspnetboilerplate, and abp.signalr.js automatically calls the hub connection before my JavaScript code is loaded.
Obviously, at that time, my hub.client.addMessageToPage isn't registered yet.
That's the common Connection started before subscriptions are added error.

Are SignalR connectionIDs hub-specific?

If I have several hubs, and connect a single JavaScript client to all of them, will the context ConnectionID be the same between them?
Interesting question. I didn't know the answer, so I tested it using this example by changing it a bit.
The Hub classes:
public class ChatHub : Hub {
public void Send(string name, string message) {
string cid = Context.ConnectionId;
Clients.All.sendMessage(name, message);
}
}
public class ChatHub2 : Hub
{
public void Send(string name, string message)
{
string cid = Context.ConnectionId;
Clients.All.sendMessage(name, message);
}
}
The page.html connecting to the hubs:
var chat = $.connection.chatHub;
var chat2 = $.connection.chatHub2;
$.connection.hub.start().done(function () {
// Call the Send method on the hub.
chat.server.send('Me', 'Message to 1');
chat2.server.send('Me', 'Message to 2');
});
I set breakpoints on the Hub methods and both are called, and Context.ConnectionId are the same. That's what I was expecting. Give it a try!
It makes sense, it supposed to use the same connection to send the message over.

SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*

In the latest version of Asp.Net SignalR, was added a new way of sending a message to a specific user, using the interface "IUserIdProvider".
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
My question is: How do I know to whom I am sending my message? The explanation of this new method is very superficial. And the draft Statement of SignalR 2.0.0 with this bug and does not compile. Has anyone implemented this feature?
More Info : http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider
Hugs.
SignalR provides ConnectionId for each connection. To find which connection belongs to whom (the user), we need to create a mapping between the connection and the user. This depends on how you identify a user in your application.
In SignalR 2.0, this is done by using the inbuilt IPrincipal.Identity.Name, which is the logged in user identifier as set during the ASP.NET authentication.
However, you may need to map the connection with the user using a different identifier instead of using the Identity.Name. For this purpose this new provider can be used with your custom implementation for mapping user with the connection.
Example of Mapping SignalR Users to Connections using IUserIdProvider
Lets assume our application uses a userId to identify each user. Now, we need to send message to a specific user. We have userId and message, but SignalR must also know the mapping between our userId and the connection.
To achieve this, first we need to create a new class which implements IUserIdProvider:
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
// your logic to fetch a user identifier goes here.
// for example:
var userId = MyCustomUserClass.FindUserId(request.User.Identity.Name);
return userId.ToString();
}
}
The second step is to tell SignalR to use our CustomUserIdProvider instead of the default implementation. This can be done in the Startup.cs while initializing the hub configuration:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var idProvider = new CustomUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
}
Now, you can send message to a specific user using his userId as mentioned in the documentation, like:
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
Here's a start.. Open to suggestions/improvements.
Server
public class ChatHub : Hub
{
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
Clients.Group(name).addChatMessage(name, message);
Clients.Group("2#2.com").addChatMessage(name, message);
}
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
Groups.Add(Context.ConnectionId, name);
return base.OnConnected();
}
}
JavaScript
(Notice how addChatMessage and sendChatMessage are also methods in the server code above)
$(function () {
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
// Create a function that the hub can call to broadcast messages.
chat.client.addChatMessage = function (who, message) {
// Html encode display name and message.
var encodedName = $('<div />').text(who).html();
var encodedMsg = $('<div />').text(message).html();
// Add the message to the page.
$('#chat').append('<li><strong>' + encodedName
+ '</strong>: ' + encodedMsg + '</li>');
};
// Start the connection.
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.sendChatMessage($('#displayname').val(), $('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
});
Testing
This is how use SignarR in order to target a specific user (without using any provider):
private static ConcurrentDictionary<string, string> clients = new ConcurrentDictionary<string, string>();
public string Login(string username)
{
clients.TryAdd(Context.ConnectionId, username);
return username;
}
// The variable 'contextIdClient' is equal to Context.ConnectionId of the user,
// once logged in. You have to store that 'id' inside a dictionaty for example.
Clients.Client(contextIdClient).send("Hello!");
Look at SignalR Tests for the feature.
Test "SendToUser" takes automatically the user identity passed by using a regular owin authentication library.
The scenario is you have a user who has connected from multiple devices/browsers and you want to push a message to all his active connections.
Old thread, but just came across this in a sample:
services.AddSignalR()
.AddAzureSignalR(options =>
{
options.ClaimsProvider = context => new[]
{
new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"])
};
});
For anyone trying to do this in asp.net core. You can use claims.
public class CustomEmailProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
Any identifier can be used, but it must be unique. If you use a name identifier for example, it means if there are multiple users with the same name as the recipient, the message would be delivered to them as well. I have chosen email because it is unique to every user.
Then register the service in the startup class.
services.AddSingleton<IUserIdProvider, CustomEmailProvider>();
Next. Add the claims during user registration.
var result = await _userManager.CreateAsync(user, Model.Password);
if (result.Succeeded)
{
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Model.Email));
}
To send message to the specific user.
public class ChatHub : Hub
{
public async Task SendMessage(string receiver, string message)
{
await Clients.User(receiver).SendAsync("ReceiveMessage", message);
}
}
Note: The message sender won't be notified the message is sent. If you want a notification on the sender's end. Change the SendMessage method to this.
public async Task SendMessage(string sender, string receiver, string message)
{
await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", message);
}
These steps are only necessary if you need to change the default identifier. Otherwise, skip to the last step where you can simply send messages by passing userIds or connectionIds to SendMessage. For more

periodically sending messages to all clients using signalr

I want to send some data from server to all connected clients using hubs after a specific interval. How can I accomplish this using signalr hubs.
Spin up the System.Threading.Timer, and from it's callback broadcast the message using specific hub.
Global.asax:
private Timer timer;
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHubs("~/signalr2");
timer = new Timer(TimerCallback(timerCallback), null, Timeout.Infinite, 1000);
}
}
Check the “Broadcasting over a Hub from outside of a Hub” section in SignalR wiki page.
Use ReactiveExtensions and then setup an Observable.Interval call. Then reactive will automatically call the lambda which can broadcast to your clients.
I have stumbled upon this post by Jason Roberts => http://dontcodetired.com/blog/post/Using-Server-Side-Timers-and-SignalR-in-ASPNET-MVC-Applications.aspx
He uses IRegisteredObject and HostingEnvironment.RegisterObject then a System.Threading.Timer in the class that does the work, I haven't tried it myself, but it looks exactly the sort of thing.
Just add
Thread.Sleep(5000);
in your send Method.
Ex:
public void Send(string name, string message)
{
Thread.Sleep(5000);
//call the broadcast message to upadate the clients.
Clients.All.broadcastMessage(name, message);
}
Hope it helps.
Edit
The following code renders the current time for every 5 seconds.
Here is script for it:
<script type="text/javascript">
$(function () {
$.connection.hub.logging = true;
$.connection.hub.start();
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
//Appending the responce from the server to the discussion id
chat.client.currentTime = function (time) {
$('#discussion').append("<br/>" + time + "<br/>");
};
// Start the connection.
$.connection.hub.start().done(function () {
//Call the server side method for every 5 seconds
setInterval(function () {
var date = new Date();
chat.client.currentTime(date.toString());
}, 5000);
});
});
</script>
<div id="discussion"></div>
And on the HubClass write the following:
public class ChatHub: Hub
{
public void currentTime(string date)
{
Clients.All.broadCastTime(date);
}
}

Resources