I am using signalr in an ASP.net application. I would like my clients to be added to groups when they connect. This way, I can separate messages by groups. I have two questions for the following code
public class MyHub : Hub, IDisconnect
{
private IMyInterface x;
public MyHub(IMyInterface y)
{
x = y;
}
public Task Join()
{
string group = x.getGroup();
return Groups.Add(Context.ConnectionId, group);
}
public Task Send(string message)
{
string group = x.getGroup();
return Clients[group].addMessage(message);
}
public Task Disconnect()
{
string group = x.getGroup();
return Clients[group].leave(Context.ConnectionId);
}
}
Are the Join() and Disconnect() methods called automatically when a client connects and disconnects? If not, where is the best place for me to call it from?
I use ninject to resolve my dependencies but it doesnt work in the above code. Are there any extra steps for me to make ninject work with a signalr class?
Anybody?
Are the Join() and Disconnect() methods called automatically when a
client connects and disconnects? If not, where is the best place for
me to call it from?
I'm not aware that Join would be a standard signalr method, however there is example in docs for detecting connect, reconnect and disconnect of the client from hub.
public class Status : Hub, IDisconnect, IConnected
{
public Task Disconnect()
{
return Clients.leave(Context.ConnectionId, DateTime.Now.ToString());
}
public Task Connect()
{
return Clients.joined(Context.ConnectionId, DateTime.Now.ToString());
}
public Task Reconnect(IEnumerable<string> groups)
{
return Clients.rejoined(Context.ConnectionId, DateTime.Now.ToString());
}
}
These are called automatically depending on client behavior and your configuration.
Related
I have a hub that manages many worker processes. I want to build a UI that lets me connect to this hub and retrieve the processing log from any of these worker processes. Essentially this will be a client wanting to obtain a string from another client. I have been able to get the request from client A sent to client B, but i dont know how to return anything in that response.
I have a simple method in the hub
public void GetRunLog(int runid)
{
JobRunLog log = null;
JobRunClient client = this.GetClientByRunID(runid);
if(client != null)
{
var rawlog = Clients.Client(client.ConnectionID).GetRunLog();
log = JsonConvert.DeserializeObject<JobRunLog>(rawlog);
Clients.Client(Context.ConnectionId).GetRunLog(log);
}
}
This request gets picked up by the client, but I dont know how to make it return a value so that var rawlog actually contains something. For the moment, this is the best workaround i could come up with.
myHubProxy.On("GetRunLog", (uiconnectionid) =>
{
string connectionid = uiconnectionid;
myHubProxy.Invoke("ReturnRunLog", run.ID, run.Log, connectionid).ContinueWith(task => {});
});
This will then make the worker client send the log back in a separate request with a reference to the client that had requested the log, but it isnt actually returning a respnonse to the initial request. I cant see a way to make this happen. Rather than use Invoke, how would i just return the object directly to the method on the hub that initiated the request?
Unfortunatelly Hub doesn't keeps it's state:
Because instances of the Hub class are transient, you can't use them
to maintain state from one method call to the next. Each time the
server receives a method call from a client, a new instance of your
Hub class processes the message. To maintain state through multiple
connections and method calls, use some other method such as a
database, or a static variable on the Hub class, or a different class
that does not derive from Hub.
Try to move the logic into a separate class and store the instance object in a static dictionary related to the connection id (don't forget to clean it). Whenewer call comes to the Hub it repoints it to a appropriate instance,
here is the simplified sample
public class TestingLogHub : Hub
{
public static readonly Dictionary<string, TestInstance> Instances =
new Dictionary<string, TestInstance>();
public void SetParameter(string value)
{
Instances[Context.ConnectionId].ContinueWith(value);
}
...
}
public class TestInstance : IDisposable
{
public TestInstance(string basePath, IHubContext host, string connectionId)
{...
}
public void ContinueWith(string value)
{
if (_nextAction == null)
{
FinishExecution();
}
else
{
try
{
_nextAction(value);
}
catch (Exception exception)
{
Error(exception.Message);
FinishExecution();
}
}
}
public void RequestParameterFor(Action<string> action, string parameter, string defaultValue = null)
{
_nextAction = action;
_host.Clients.Client(_connectionId).requestParameter(parameter, defaultValue??GetRandomText());
}
}
So when Instance is started it's doing some work, but at the moment it requires some input it executes RequestParameterFor that set's the next function to be executed into an instance state and waits for the next call of ContinueWith.
it is a bit generic example, in your case you can send back an object and provide it to an instance, and maybe dispose the instance at the end of that request, if that was the only required call
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>();
Maybe I am just not understanding Groups in SignalR correctly, but I am confused on the registering a user to a group part. I am using version 1.2.2.
Here: Groups demonstrates how to use groups in older SignalR
I am using a singleton to maintain the context of the hub. I am also using asp.net mvc 4. Basically, I want to do something with a menu item (make it flash, add a count of new tasks, etc..) during an update, but only to the users that are assigned tasks within that option.
So I figured, server side when checking the user's roles, I can conditionally assign them to the SignalR Group for broadcasts.
Here is my hub, and singleton classes:
public class TransactHub : Hub
{
public Task RegisterForTransactionPartUpdates()
{
return Groups.Add(Context.ConnectionId, "Transact");
}
public void UpdateDailyTransactionTable(string r)
{
Clients.All.broadcastUpdate(r);
}
}
And Singleton:
public class TransactSingleton
{
private readonly static Lazy<TransactSingleton> _instance = new Lazy<TransactSingleton>(() => new TransactSingleton(GlobalHost.ConnectionManager.GetHubContext<TransactHub>().Clients));
private TransactSingleton(IHubConnectionContext clients)
{
Clients = clients;
}
private IHubConnectionContext Clients
{
get;
set;
}
public static Transact Instance
{
get
{
return _instance.Value;
}
}
public void RegisterForTransactionUpdates()
{
//I want to register user here..
}
public void BroadcastUpdate(List<string> orders)
{
}
}
So where would I actually register the user? Also, upon a new connection using:
$.connection.hub.disconnected(function () {
setTimeout(function () {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
Will the user stay registered in the group?
Hmm, so it appears from this article: Hubs
public class ContosoChatHub : Hub
{
public override Task OnConnected()
{
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
}
public override Task OnDisconnected()
{
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected();
}
public override Task OnReconnected()
{
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
}
}
I should be using the OnConnected, and make a database call to handle the conditional assigning. I was hoping I would be able to handle the database call outside of the hub, but doesn't look possible. Correct me if I am wrong.
This is related to SignalR + posting a message to a Hub via an action method, but my question is a bit different:
I'm on version 0.5.2 of signalr, using hubs. In older versions, you were encouraged to create methods on the hub to send messages to all clients, which is what I have:
public class MyHub : Hub
{
public void SendMessage(string message)
{
// Any other logic here
Clients.messageRecieved(message);
}
...
}
So in 0.5.2, I want to send a message to all the clients (say from somewhere in the controller). How can I get access to the MyHub instance?
The only way I've seen referenced is:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.messageRecieved("hello");
Which is fine, but I want to call the method on my hub.
You can do this by using a static method:
SignalR v.04-
public class MyHub : Hub
{
internal static void SendMessage(string message)
{
var connectionManager = (IConnectionManager)AspNetHost.DependencyResolver.GetService(typeof(IConnectionManager));
dynamic allClients = connectionManager.GetClients<MyHub>();
allClients.messageRecieved(message);
}
...
}
SignalR 0.5+
public class MyHub : Hub
{
internal static void SendMessage(string message)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.messageRecieved(message);
}
...
}
You can then call this like so:
MyHub.SendMessage("The Message!");
Good article on the SignalR API: http://weblogs.asp.net/davidfowler/archive/2012/05/04/api-improvements-made-in-signalr-0-5.aspx
Provided by Paolo Moretti in comments
I had same problem, in my example addNotification is client-side method:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SignalR.NotificationsHub>();
hubContext.Clients.addNotification("Text here");
On you client side you can add code to call your hub method in addNotification:
var notification = $.connection.notificationHub;
notification.addNotification = function (message) {
notification.addServerNotification(message); // Server Side method
}
$.connection.hub.start();
Hub:
[HubName("notificationHub")]
public class NotificationsHub : Hub
{
public void addServerNotification(string message)
{
//do your thing
}
}
UPDATE:
Reading your question over and over agian I really don't find a reason to do that. Hub methods are usually there to be called from client side, or I misunderstood you, anyway here is an update. If you want to do a server side thing and then notify clients.
[HttpPost]
[Authorize]
public ActionResult Add(Item item)
{
MyHubMethodCopy(item);
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SignalR.NotificationsHub>();
hubContext.Clients.addNotification("Items were added");
}
private void MyHubMethodCopy(Item item)
{
itemService.AddItem(item);
}
Update for ASP.NET Core 2.x and 3.x:
You can easily access the Hub instance anywhere that you have Dependency Injection:
public class HomeController : Controller
{
private readonly IHubContext<MyHub> _myHubContext;
public HomeController(IHubContext<MyHub> myHubContext)
{
_myHubContext = myHubContext;
}
public void SendMessage(string msg)
{
_myHubContext.Clients.All.SendAsync("MessageFromServer", msg);
}
}
If it gives you syntax errors, make sure you have:
using Microsoft.AspNetCore.SignalR;
and that you do NOT HAVE:
using Microsoft.AspNet.SignalR;
I'm building a hub which should send message to specific user and this Question and this Question would work perfectly, only I can't find AddToGroup method, I know a lot things have changed in version 0.5, is this one of them?
Yes, there's a Groups property now on the Hub class that you could use to add users to. The documentation also illustrates this:
public class MyHub : Hub, IDisconnect
{
public Task Join()
{
return Groups.Add(Context.ConnectionId, "foo");
}
public Task Send(string message)
{
return Clients["foo"].addMessage(message);
}
public Task Disconnect()
{
return Clients["foo"].leave(Context.ConnectionId);
}
}