I'm new to SignalR but I understand the concept.
My goal is to expose web services which when called will update all connected clients. I've built a little knockout js to connect to the hub and that works well. When I click my start button it sends a message to each of the clients connected like it is supposed to. Here is the code for a background.
Hub:
[HubName("realTimeData")]
public class RealTimeDataHub : Hub
{
public void UpdateFlash(string message)
{
Clients.flashUpdated(message);
}
public void clear()
{
Clients.cleared();
}
}
JS:
function viewModel(){
var self = this;
self.messages = ko.observableArray([]);
self.hub = $.connection.realTimeData;
self.hub.flashUpdated = function (m) {
self.messages.push(new message(m));
};
self.hub.cleared = function () {
self.messages.removeAll();
};
self.Start = function () {
self.hub.updateFlash("Updated Flashed from client");
};
self.Stop = function () {
self.hub.clear();
};
$.connection.hub.start();
}
ko.applyBindings(new viewModel());
MVC API:
public class HomeController : Controller
{
public ActionResult Update()
{
GetClients().updateFlash("From server");
return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet);
}
private static dynamic GetClients()
{
return GlobalHost.ConnectionManager.GetHubContext<RealTimeData.Hubs.RealTimeDataHub>().Clients;
}
}
The problem:
When I call the "Update" method from my controller (Home/Update, not a real API yet) it sends one signal for each of the clients connected. So if I have 2 clients connected, I will get 2 "From Server" messages to each client and so on. I would only like the one...
Any suggestions? Anything is appreciated
Thanks,
Matt
Solution:
Must import 'using SignalR.Client;'
public ActionResult Update()
{
hub.Invoke("UpdateFlash", "From the Home Controller");
//GetClients().updateFlash("From server");
return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet);
}
Here is where I found the solution:
https://github.com/SignalR/SignalR/wiki/SignalR-Client-Hubs
I hope this can help somebody out.
Matt
Related
I'm trying to send a message via SignalR from my WebRole to the client but it's not appearing. It appears fine when I test call it from a Controller, but when called from the Run() function, it doesn't seem to make it over to the client.
public override void Run()
{
processingQueueClient.OnMessage((message) =>
{
message.Complete();
MainHub.Send("Test 1");
});
completedEvent.WaitOne();
}
namespace MainWebRole
{
public class MainHub : Hub
{
public static void Send(string message)
{
var context = GlobalHost.ConnectionManager.GetHubContext<MainHub>();
context.Clients.All.broadcastMessage(message);
}
}
}
<script>
$(function () {
var chat = $.connection.mainHub;
chat.client.broadcastMessage = function (message) {
var notificationText = "<div><button type=\"button\" class=\"btn btn-default\" onclick=\"onClickClearComplete()\">Clear Complete</button></div><div class=\"spacer10\"></div><div><table class=\"table table-bordered\">";
notificationText += "<tr><td nowrap><span><i class=\"fa fa-pause\"></i> Pending \"" + message + "\"</span></td></tr>";
notificationText += "</table></div>";
$("#statusText").html(notificationText);
};
$.connection.hub.start().done(function () {
});
});
</script>
Please try to run your signalR send function via below steps:
1) Install-Package Microsoft.AspNet.SignalR.Client
2) write below code in Azure web role in run function.
HubConnection _hub = new HubConnection("http://localhost:1942");
var _proxy = _hub.CreateHubProxy("MainHub");
if (_hub.State == ConnectionState.Disconnected)
{
await _hub.Start();
}
await _proxy.Invoke("Send", "jambor");
http://localhost:1942 is your SignalR server site.
MainHub is your SignalR hub class name
Send is your function in MainHub class.
I have a problem in implementing SignalR in my MVC application. I am updating my posts like count every time a person clicks on like button of a specific album post page. But I am facing a problem here that when I click on Like button of a specific page, the likes count of the other pages is also incremented.
so here is my NotificationHub code:
public class NotificationHub : Hub
{
public void Like(Guid albumId, Guid managerId)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
var LikeCounter = SaveLike(albumId, managerId);
Clients.All.updateLikeCount(LikeCounter);
}
public int? SaveLike(Guid albumId, Guid managerId)
{
// my implementation code
}
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
}
This is my jQuery code:
$(function () {
var albumClient = $.connection.notificationHub;
albumClient.client.updateLikeCount = function (likes) {
var counter = $(".like-count");
$(counter).fadeOut(function () {
$(this).text(likes);
$(this).fadeIn();
});
};
$(".like-button").on("click", function () {
var albumId = $(this).attr("data-id");
var managerId = $(this).attr("id");
albumClient.server.like(albumId, managerId);
});
$.connection.hub.start();
});
So kindly help me what I am doing wrong or what modifications are needed in this code.
First let me point out few mistakes in the code.
$(function () {
var albumClient = $.connection.notificationHub;
albumClient.client.updateLikeCount = function (likes) {
var counter = $(".like-count"); //selects all like button. Thats why every page buttons are updated.
$(counter).fadeOut(function () {
$(this).text(likes);
$(this).fadeIn();
});
};
$(".like-button").on("click", function () {
var albumId = $(this).attr("data-id");
var managerId = $(this).attr("id");
albumClient.server.like(albumId, managerId); // you are calling the server method and the server has no clue of which button to update.
});
$.connection.hub.start();
});
So as pointed out in the comments. Firstly you are calling the server by passing some data which is fine. But the server has to later send ping to the client and update what?? which button?? it has no clue. And you are using a class selector for the buttons $(".like-count") which will select all the buttons with that class name, And later you update all the buttons because of this.
So the solution is each of your button must have a unique id like to which page it belongs or any other data which makes it unique. You can still have the same class and same event binded to on click. Only change would be like below.
$(".like-button").on("click", function () {
var albumId = $(this).attr("data-id");
var managerId = $(this).attr("id");
albumClient.server.like(albumId, managerId, $(this).attr('id')); //pass the id of the like button as well to your signalR code
});
Modify your SignalR code to below.
public void Like(Guid albumId, Guid managerId,string buttonID) //include parameter to accept button Id
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
var LikeCounter = SaveLike(albumId, managerId);
Clients.All.updateLikeCount(LikeCounter,buttonID); //pass back the button id as well.
}
Now to the last change.
albumClient.client.updateLikeCount = function (likes,btnId) { // input parameter
//directly change the button likes.
$('#'+btnId).fadeOut(function () {
$(this).text(likes);
$(this).fadeIn();
});
};
let me know if this helps
You need to add Users to a Groups on page load:
this.Groups.Add(this.Context.ConnectionId, postName)
then:
Clients.Group(postName).updateLikeCount(LikeCounter);
My connection does not start.
This code worked in 1.x but in version 2 is not working.
SignalR seems to be trying to connect but without success.
The hub method is never called.
Attached sending an image with SignalR debug.
Javascript:
<script type="text/javascript">
$.connection.hub.logging = true;
var options = { transport: ['webSockets', 'longPolling'] };
$(function() {
var userHub = $.connection.userHub;
//Iniciar connecção
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
</script>
Hub:
[HubName("userHub")]
public class UserHub : Hub
{
public void Ini()
{
Clients.Client(Context.ConnectionId).iniDone(string.Format("Conectado com o id: {0}", Context.ConnectionId));
}
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Add(connectionId);
return base.OnConnected();
}
public override Task OnDisconnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Remove(connectionId);
return base.OnDisconnected();
}
}
Debug:
SignalR Debug Image
EDIT:
I found the problem! The GetInstance method of my Singleton has problems.
public static UserData GetInstance(string username)
{
if (_sharedUsers == null)
lock (_lockCreate)
_sharedUsers = new Dictionary<string, UserData>();
if (!_sharedUsers.ContainsKey(username))
lock (_lockAdd)
_sharedUsers.Add(username, new UserData(username));
return _sharedUsers[username];
}
the method stops always here: lock (_lockAdd)
I want to save all user connectionsIds Any ideas?
Thanks
Try moving the client method subscription to be before you connect. If it's not registered by the time the connection is started, then it will not be callable from the server.
So change it to the following:
$(function() {
var userHub = $.connection.userHub;
//Register Client handlers first
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
//Now you can connect.
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
Edit
Based on your comment around a server error in the OnConnected method, it seems like you may have a two problems then. Isolate the connection tracking part out (just comment it out) to get the full round-trip going between client and server. Then add back the connection tracking which is possibly a DB connection error - check the server logs.
Edit
In terms of storing the user connections, you've a few options.
Use ConcurrentDictionary:
One of the simplest is storing in a static ConcurrentDictionary, similar to what you have. Try to avoid the use of so many locks - using a ConcurrentDictionary means you'll actually end up with none.
e.g.
public class UserData
{
public UserData(string username)
{
UserName = username;
ConnectionIds = new HashSet<string>();
}
public string UserName { get; private set; }
public HashSet<string> ConnectionIds { get; private set; }
}
public static class ConnectionStore
{
private static readonly ConcurrentDictionary<string, UserData> _userData = new ConcurrentDictionary<string, UserData>();
public static void Join(string username, string connectionId)
{
_userData.AddOrUpdate(username,
u => new UserData(u), /* Lambda to call when it's an Add */
(u, ud) => { /* Lambda to call when it's an Update */
ud.ConnectionIds.Add(connectionId);
return ud;
});
}
}
See MSDN for more info: http://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx
Use a database:
The other option is to store in a database (using Entity Framework) which has the added benefit of tracking user data across server recycles.
Have a look at http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections which shows all these options a couple of others.
Had the same problem for so long, so gave up the whole signalR at some point, but had to pick it up again for our project:
I have written an answer which might lead you and others on the right track (step by step)...In the answer I am using PersistentConnection rather than Hub, but the principle should be the same:
https://stackoverflow.com/a/25304790/3940626
I have javascript client code:
<script>
$(function () {
var chat = $.connection.notificationHub;
chat.client.newMessage = function (message) {
alert(message);
// Add the message to the page.
$('#messages').append('<li><strong>' + message + '</strong>: </li>');
};
$.connection.hub.start().done();
});
</script>
My hub class in an dll name -- signalcomponent
public class NotificationHub : Hub
{
public async Task SendAll(string message)
{
await Clients.All.newMessage(message);
}
}
my signalrcomponent is now used in another website(backend) from where i want to send message to another website, but using the same hub.
In backend website, i use signalrcomponent dll and samehub
now i try to call the "newMessage" method of other website/client
using following code:
public class NotificationBroadcaster
{
private IHubContext _hubContext;
public NotificationBroadcaster()
{
_hubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
}
public async Task SendAll(string message)
{
await _hubContext.Clients.All.newMessage(message);
}
}
But this is not working
help please
I've been trying to get my WPF client app to receive a SignalR message sent by the WCF service. I've tried many things and have now resorted to hacking away in the hopes that something just works. I've followed tutorials and examples online, and I simply can't get my WPF OnSignalRMessage() method to get called. Where am I going wrong here?
My hub:
public class PrestoHub : Hub
{
public void Send(string message)
{
Clients.All.OnSignalRMessage(message);
}
}
My startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HubConfiguration { EnableCrossDomain = true };
app.MapHubs("http://localhost:8084", config);
}
}
The method that starts my SignalR host (within my WCF service host):
private void StartSignalRHost()
{
const string url = "http://localhost:8084";
WebApplication.Start<Startup>(url);
}
The code to actually send some message:
GlobalHost.ConnectionManager.GetHubContext<PrestoHub>().Clients.All.OnSignalRMessage("snuh");
Console.WriteLine("Sent 'snuh' to all clients...");
My WPF client methods:
private void InitializeSignalR()
{
var hubConnection = new Connection("http://localhost:8084");
hubConnection.Start();
hubConnection.Received += OnSignalRMessage;
}
private void OnSignalRMessage(string data)
{
MessageBox.Show(data);
}
While I'm still struggling to understand the how and why, I was able to get it working. +1 to N. Taylor Mullen for pointing me in the right direction. In addition to his suggestion on the client side, I had to change some server code as well, namely using an empty hub and a simplified Startup class.
My hub:
public class PrestoHub : Hub{}
Note: The hub is empty because we're not calling methods within it. As we'll see later, we get the hub context and send messages to the clients.
My startup class:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapHubs();
}
}
The above code seems to be what fixed the problem. This also works:
var config = new HubConfiguration { EnableCrossDomain = true };
app.MapHubs(config);
But as soon as I specify a URL, my client doesn't receive the messages (tried with and without the "SignalR" part):
app.MapHubs("http://localhost:8084/SignalR", config);
The method that starts my SignalR host (within my WCF service host):
private void StartSignalRHost()
{
const string url = "http://localhost:8084";
WebApplication.Start<Startup>(url);
}
The code to actually send some message:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<PrestoHub>();
hubContext.Clients.All.OnSignalRMessage("snuh");
My WPF client method:
private void InitializeSignalR()
{
var hubConnection = new HubConnection("http://localhost:8084");
var prestoHubProxy = hubConnection.CreateHubProxy("PrestoHub");
prestoHubProxy.On<string>("OnSignalRMessage", (data) =>
{
MessageBox.Show(data);
});
hubConnection.Start();
}
You're creating a PersistentConnection not a hub connection. In order to get messages from your PrestoHub you first need to connect with a HubConnection and then you need to handle the event "OnSignalRMessage".
So your client code would now look like:
private void InitializeSignalR()
{
var hubConnection = new HubConnection("http://localhost:8084");
var prestoHubProxy = hubConnection.CreateHubProxy("PrestoHub");
// Bind the "OnSignalRMessage" to a function
prestoHubProxy.On<string>("OnSignalRMessage", (data) => {
MessageBox.Show(data);
});
hubConnection.Start();
}
If your methods on the server side are asynchronous make sure they return a task instead of void. That is you should have
public async Task Method(){ }
and not
public async void Method(){ }