Invoke a SignalR hub from .net code as well as JavaScript - c#

I have a SignalR hub, which i successfully call from JQuery.
public class UpdateNotification : Hub
{
public void SendUpdate(DateTime timeStamp, string user, string entity, string message)
{
Clients.All.UpdateClients(timeStamp.ToString("yyyy-MM-dd HH:mm:ss"), user, entity, message);
}
}
update message sent successfully from JS like so
var updateNotification = $.connection.updateNotification;
$.connection.hub.start({ transport: ['webSockets', 'serverSentEvents', 'longPolling'] }).done(function () { });
updateNotification.server.sendUpdate(timeStamp, user, entity, message);
and received successfully like so
updateNotification.client.UpdateClients = function (timeStamp, user, entity, message) {
I can't work out how to call sendUpdate from within my controller.

From your controller, in the same application as the hub (rather than from elsewhere, as a .NET client), you make hub calls like this:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<UpdateNotification>();
hubContext.Clients.All.yourclientfunction(yourargs);
See Broadcasting over a Hub from outside of a Hub near the foot of https://github.com/SignalR/SignalR/wiki/Hubs
To call your custom method is a little different. Probably best to create a static method, which you can then use to call the hubContext, as the OP has here: Server to client messages not going through with SignalR in ASP.NET MVC 4

Here's an example from SignalR quickstart
You need to create a hub proxy.
public class Program
{
public static void Main(string[] args)
{
// Connect to the service
var hubConnection = new HubConnection("http://localhost/mysite");
// Create a proxy to the chat service
var chat = hubConnection.CreateHubProxy("chat");
// Print the message when it comes in
chat.On("addMessage", message => Console.WriteLine(message));
// Start the connection
hubConnection.Start().Wait();
string line = null;
while((line = Console.ReadLine()) != null)
{
// Send a message to the server
chat.Invoke("Send", line).Wait();
}
}
}

Related

Signal R in Web Api

I have Web Api which serves to CRUD Posts from Web App, Android App and Desktop.
I want to add SignalR to the Web Api, every time when Action Create in Controller gets called I want to notify all users that Post is created.
Problem is, I can't find any implementation in only Web Api, all implementations are in Web App with Web Api or something like that. I read all MSDN documentation about it. I'm strugling for 3-4 days now.
I managed to get to the point where I implemented SignalR, and my server isn't created any signalr/hubs file that I need to call from Web App script. It's only created when I run app locally - if I publish it on Azure that file isn't created.
Anyone have concrete steps for Implementation only in Web Api?
I tried this blog, it has Web Api stuff but have js in project and local html added. It's not standalone REST api.
This is not about not creating signalr/hubs file. It's about creating standalone Web Api with SignalR.
I have startup:
public void Configuration(IAppBuilder app) {
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
app.MapSignalR("/signalr", new Microsoft.AspNet.SignalR.HubConfiguration());
}
My Hub:
public class ServiceStatusHub : Hub {
private static IHubContext hubContext =
GlobalHost.ConnectionManager.GetHubContext<ServiceStatusHub>();
public static void SetStatus(string message) {
hubContext.Clients.All.acknowledgeMessage(message);
}
}
And in my Api Controler I call:
ServiceStatusHub.SetStatus("Status changed!");
I made console application to test Api, added Signal R client and class:
class SignalRMasterClient {
public string Url { get; set; }
public HubConnection Connection { get; set; }
public IHubProxy Hub { get; set; }
public SignalRMasterClient(string url) {
Url = url;
Connection = new HubConnection(url, useDefaultUrl: false);
Hub = Connection.CreateHubProxy("ServiceStatusHub");
Connection.Start().Wait();
Hub.On<string>("acknowledgeMessage", (message) =>
{
Console.WriteLine("Message received: " + message);
/// TODO: Check status of the LDAP
/// and update status to Web API.
});
}
public void SayHello(string message) {
Hub.Invoke("hello", message);
Console.WriteLine("hello method is called!");
}
public void Stop() {
Connection.Stop();
}
}
program.cs:
var client = new SignalRMasterClient("myUrl.com/signalr");
// Send message to server.
client.SayHello("Message from client to Server!");
I getting 500 Internal Server Error.
How can I test is my Web Api signalR works for sure?
I see some problems:
1) You do not need the hubContext field in your hub. You inherit from Hub. This class contains allready a "Clients" property .
public class ServiceStatusHub : Hub {
public static void SetStatus(string message) {
Clients.All.acknowledgeMessage(message);
}
}
2) Log errors at starting of server.
public SignalRMasterClient(string url) {
Url = url;
Connection = new HubConnection(url, useDefaultUrl: false);
Hub = Connection.CreateHubProxy("ServiceStatusHub");
Connection.Start().ContinueWith(task => { if (task.IsFaulted) {
Console.WriteLine("There was an error opening the connection:{0}",
task.Exception.GetBaseException());
} else {
Console.WriteLine("Connected");
}
});
Hub.On<string>("acknowledgeMessage", (message) =>
{
Console.WriteLine("Message received: " + message);
/// TODO: Check status of the LDAP
/// and update status to Web API.
}).Wait();
}
3) Check Client url. You need no signalr there in your path at creation of your client.
just:
var client = new SignalRMasterClient("myUrl.com/");
Here you will find a running sample which does all you need:
SignalR Console app example

Client not receiving SignalR message from controller

I am trying to send a message from a controller to client(s) group using SignalR.
When an event occurs in one of my controllers, I need to push a message using a signalr hub to be displayed like an alert on my clients screens.
i know a lot of questions have been asked here, and I've read and tried a lot of them. Since I am new to SignalR some of them even already helped me to put things in place.
At the moment everything seems in place. Clients can connect to the hub and join groups and controller can call methods from the hub. But the client never receive the message and I can't figure out why. I suspect that hub's method called by controller don't "see" the clients but I can't understand what's wrong.
Hub's code :
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
[HubName("myHub")]
public class MyHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
public void Notify(string groupName, string message)
{
Clients.Group(groupName).displayNotification(message);
}
public static void Static_Notify(string groupName, string message)
{
var toto = UserHandler.ConnectedIds.Count();
hubContext.Clients.Group(groupName).displayNotification(message);
hubContext.Clients.All.displayNotification(message);//for testing purpose
}
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool StopCalled)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnected(StopCalled);
}
}
Here is the call from my controller :
//(For simplification and readability I define here variables actually obtained by treating some data )
//I already checked that problem did not come from missing data here
string groupName = "theGroupName";
string message = "My beautifull message.";
//prepare signalR call
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
//Following commented lines are different attempts made based on exemples and answers I found here and on others sites.
//The uncommented one is the one in use at the moment and is also based on an answer (from SO i think)
//context.Clients.Group(_userEmail).displayNotification(message);
//context.Clients.Group(_userEmail).Notify(_userEmail,message);
MyHub.Static_Notify(_userEmail, message);
And here is the client-side code :
$(document).ready(function () {
var userGroup = 'theGroupName';
$.connection.hub.url = 'http://localhost/SignalRHost/signalr';
var theHub = $.connection.myHub;
console.log($.connection)
console.log($.connection.myHub)
theHub.client.displayNotification = function (message) {
console.log('display message');
alert(message);
};
$.connection.hub.start()
.done(function () {
theHub.server.joinGroup(userGroup);
console.log("myHub hub started : " + $.connection.hub.id)
console.log(theHub)
})
.fail(function () {
console.log('myHub hub failed to connect')
});
});
Please help me understand what logic I failed to understand or what error I am missing.
EDIT :
To answer Alisson's comment:
The Startup.cs I forgot to show
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR();
});
}
Important stuff I forgot to mention too :
The Controller, the hub, and the client are 3 different projects (because of the global application architecture I had to separate hub logic)
All are on my localhost IIS but on different ports
I've set breakpoints on "OnConnected" and "onDiconnected" events and client connects and disconnects normally
You just need one app to act as server, in your case it should be the SIgnalRHost project. Your controller project should be a client of the server, therefore it just needs this package:
Install-Package Microsoft.AspNet.SignalR.Client
Your controller project doesn't actually need to reference the project containing the hub class. In your controller, you'll use C# SignalR client to connect to the server (as you would do in javascript client), join the group and invoke the hub method:
var hubConnection = new HubConnection("http://localhost/SignalRHost/signalr");
IHubProxy myHub = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
myHub.Invoke("JoinGroup", "theGroupName");
myHub.Invoke("Notify", "theGroupName", "My beautifull message.");
...finally, you don't need that Static_Notify at all.
Since you are passing the group name as a parameter on the Notify
method, you don't really need to join the group from your controller.
You'd need only if you were trying to send the message to the same
group the controller was connected (then you wouldn't need to pass the
group name as parameter anymore).
SignalR C# Client Reference.

how to send a binary file to SignalR client from server in dotnet core

We had a solution for sending a file to SignalR client using .Net
We have now moved to .Net Core
In previous .net solution, we used to Hub context via GlobalHost.ConnectionManager
var myHub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
myHub.Clients.Client(connectionId).doStuffWithFile(fileByteArray, fileName);
where in the client side, function doStuffWithFile would be triggered with the two arguments.
In new .Net Core solution I created a Hub class by deriving from Hub. I added a method of Send to send a file to specific client and not broadcasting it to every one
public class MyHub : Hub
{
private static string _connectionId;
public override Task OnConnectedAsync()
{
_connectionId = Context.ConnectionId;
return Task.CompletedTask;
}
public override Task OnDisconnectedAsync(Exception exception)
{
_connectionId = Context.ConnectionId;
//// remove Connection Id
return base.OnDisconnectedAsync(exception);
}
public async Task Send(byte[] fileByteArray, string fileName)
{
await Clients.Client(_connectionId).InvokeAsync("doStuff", fileByteArray, fileName);
}
}
However, I do not have any mechanism in .Net core such as GlobalHost or ConnectionManager to get HubContext to send the file.
On the client side:
static void Main(string[] args)
{
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:25786/file")
.WithConsoleLogger()
.Build();
connection.On<byte[], string>("doStuff", DoStuff);
connection.StartAsync().ContinueWith(
task =>
{
if (task.IsFaulted)
{
Console.WriteLine("Connection faulty");
}
});
Console.ReadLine();
}
private static void DoStuff(byte[] data, string name)
{
File.WriteAllBytes(#"c:\Projects\" + name, data);
}
I tried to create a new instance of MyHub to invoke the Send method, but simply it does not work. Could you please advise me how to do this?
This is not a direct answer to your question but hopefully it will help you find a solution.
Storing connection Id in a static class variable is not correct. It will change each time a new client connects and you won't have any control over which client you are sending to. From the code you provided it is not clear how you know which client to send the file to. Note that you also set the _connectionId when the client is disconnected so it is likely that you will try to send data to the connection you know has been closed. I actually think you will want to pass the target connection id or the user to the hub Send method. I think you may not have enough context in the hub method itself to resolve the connection id but since connection id is a SignalR concept it might be hard to access it outside SignalR components. This is why it might be easier to use the user instead of connection Id (i.e. Clients.User(...) instead of Clients.Client(...)).
GlobalHost no longer exists in the new SignalR. Instead you can inject IHubContext<THub> to the class you want to invoke the method from and use InvokeAsync. Here is an example of invoking a hub method from an Mvc Controller.

Unable to track connected users using Signalr hub class

I have created a class that inherits the Signalr Hub class and it runs on startup. When a connection is made, there are some custom headers sent from the client that I use to generate a user object. I want to store these in memory on the server so that I can retrieve the list and display them in a UI. Someone can then use this UI to see the user info and perform interactions with this connection. I have setup a hub class in an ASP MVC project and i am using a console app for the client. I can connect fine and the server can communicate back, but the property that I use in the hub class to keep track of the connected users is reset to null every time a request is made to the hub class.
public class JobRunHandler : Hub
{
private List<JobRunClient> RunningJobs { get; set; }
public JobRunHandler()
{
if(this.RunningJobs == null) this.RunningJobs = new List<JobRunClient>();
}
public override Task OnConnected()
{
JobRunClient runclient = new JobRunClient()
{
ConnectionID = Context.ConnectionId,
Someotherstuff = this.GetHeaderInt(Context.Headers["Someotherstuff"])
};
this.RunningJobs.Add(runclient);
return base.OnConnected();
}
public override Task OnReconnected()
{
var existingClient = this.GetConnectingClient();
if (existingClient == null)
{
JobRunClient runclient = new JobRunClient()
{
ConnectionID = Context.ConnectionId,
Someotherstuff = this.GetHeaderInt(Context.Headers["Someotherstuff"])
};
this.RunningJobs.Add(runclient);
}
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
this.RemoveClient(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public void TestMethod()
{
Clients.All.ping();
var client = this.GetConnectingClient();
}
}
I have put break points in every method so i know when it runs. The client never disconnects or triggers reconnect, so there is no issue with the connection being broken. The client connects and the OnConnected() method triggers and the value is added to this.RunningJobs. The client then calls the TestMethod() which works, but when i check this.RunningJobs it is empty.
When Clients.All.ping(); runs it does actually send a ping back to the client. So the connection is made successfully, the server maintains the connection and i can send a ping back to the client when a separate method is called, but for some reason the property is being reset and I dont know why. I can use redis for this if I have to, but I have seen others use this strategy and its not been an issue.
Here is the client code I have created to test this (the console app)
var hubConnection = new HubConnection("http://localhost:2497/");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
IHubProxy myHubProxy = hubConnection.CreateHubProxy("JobRunHandler");
myHubProxy.On("ping", () => Console.Write("Recieved ping \n"));
hubConnection.Headers.Add("Someotherstuff", "1");
hubConnection.Start().Wait();
while(true)
{
myHubProxy.Invoke("BroadcastCompletion").ContinueWith(task =>
{
if (task.IsFaulted)
{
Console.WriteLine("!!! There was an error opening the connection:{0} \n", task.Exception.GetBaseException());
}
}).Wait();
Console.WriteLine("Broadcast sent to the server.\n");
Thread.Sleep(4000);
}
The hub is transient. SignalR creates a hub instance each time a hub method is invoked so you cannot store any state in an instance property between the calls.
I changed the property being used for this to a ConcurrentDictionary and this seems to be doing the trick. It allows me to store the client connections across all connections. I used the following code for this.
private static readonly ConcurrentDictionary<string, JobRunClient> RunningJobs = new ConcurrentDictionary<string, JobRunClient>();

Signalr connects but doesn't push a message

I've been working on implementing signalr as part of a wcf service to talk to a .net client. Apart form a connection message all communication is one way passing a dynamic payload to the client side.
I've managed to set it up so that the client will connect to the service and pass a connection message but I can't get the pushing of a message from the service to the client.
Sorry if I've missed this answered else where but I've been unable to find a reason for this failing as it seems to follow the "how to's"
Any help would be much appreciated and thank you in advance
Server side:
WCF external call
public class MessageService : IMessageService
{
public string PushAlerts()
{
var payLoad = new PayLoad
{
MethodName = "alerts"
};
IHubContext connectionHub = GlobalHost.ConnectionManager.GetHubContext<PushConnection>();
connectionHub.Clients.All.Notify(payLoad);
}
}
My Hub
[HubName("PushHub")]
public class PushHub : Hub
{
public override Task OnConnected()
{
var connectionMessage = Context.QueryString["CONNECTION MESSAGE"];
if (connectionMessage != null)
{
Debug.WriteLine("connectionMessage");
}
return base.OnConnected();
}
}
ClientSide:
var querystringData = new Dictionary<string, string>{};
querystringData.Add("CONNECTION MESSAGE", "foo Connection");
var hubConnection = new HubConnection("http://localhost:60479/", querystringData); //Running local till working
hubConnection.TraceLevel = TraceLevels.All;
hubConnection.TraceWriter = Console.Out;
IHubProxy clientHubProxy = hubConnection.CreateHubProxy("PushHub");
clientHubProxy.On("Notify", payLoad =>
SynchronizationContext.Current.Post(delegate
{
ResponseMethod(payLoad);
}, null)
);
await hubConnection.Start();
I've missed out payload but that only holds a string value at present. I've also setup a pipemodule for logging perposes.
Thanks Again
Ok so I resolved this problem in two ways firstly I moved the call to the client inside the hub its self, which I then called from a method in my wcf service.
[HubName("PushHub")]
public class PushHub : Hub
{
IHubContext connectionHub = GlobalHost.ConnectionManager.GetHubContext<PushConnection>();
public void Send(Payload payload)
{
connectionHub.Clients.All.Notify(payLoad);
}
}
Secondly the client code for the method was all wrong. In the end this worked:
clientHubProxy.On("Notify", (payLoad) => { dostuff };
Took a lot of fiddling but hope my answer helps others.

Categories