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);
}
}
Related
In a lot of our services, the OnStart method calls a neverEnding async Task and .Wait() which does all the service work until it gets manually shut down. We do something like the following (and have been since before I came onto the scene).
MyServiceClass service;
protected override void OnStart(string[] args)
{
eventLog.WriteEntry("MyService service is starting up.");
service = new MyServiceClass(this.ServiceName);
}
...
public class MyServiceClass
{
public MyServiceClass() {
Task.Run(() => DoServiceWork()).Wait();
}
public async Task DoServiceWork() {
while (true)
{
await Task.Delay(1000);//pretend this is actual work being done
}
}
}
Is there any reason not to just store the task and move on? (assuming the main method has appropriate exception handling)
public class MyServiceClass
{
public Task taskToBeAwaitedOnServiceStop;
public MyServiceClass() {
taskToBeAwaitedOnServiceStop = Task.Run(() => DoServiceWork());
}
}
or just to ignore the task entirely
public class MyServiceClass
{
public MyServiceClass() {
_ = DoServiceWork();
}
}
I'm considering the options as we don't like having .waits floating around (and having less means less to check when we go hunting). I'm asking here to be certain my alternatives won't cause strange service logic if implemented.
I hope this isn't too stupid a question, thanks for any help :)
I have an ASP.NET MVC 5 Application with a SignalR 2 hub and using autofac for the DI.
The entire business logic is encapsulated in manager classes in their own layer. Some manager methods need informations about the current logged in user (UserId, TenantId, ..).
I solved this problem by injecting an AuthorizationProvider into each manager class that needs the user information.
public interface IAuthorizationProvider
{
long? GetUserId();
long? GteTenantId();
}
public class MyManager : IMyManager
{
private IAuthorizationProvider _authorizationProvider;
public MyManager(IAuthorizationProvider authorizationProvider)
{
_authorizationProvider = authorizationProvider;
}
public void MyMethod()
{
// Getting the User information here is pretty simple
long userId = _authorizationProvider.GetUserId();
}
}
Normally I can get the user information from the HttpContext and from the session. So I wrote a SessionAuthorizationProvider:
public class SessionAuthorizationProvider{
public long? GetUserId()
{
HttpContext.Current?.Session?[SessionKeys.User]?.Id;
}
public long? GteTenantId() { ... }
}
But now I have a new method in the SignalR hub that use the same mechanism.
[HubName("myHub")]
public class MyHub : Hub
{
private IMyManager _myManager;
public MyHub(IMyManager myManager)
{
_myManager = myManager;
}
[HubMethodName("myHubMethod")]
public void MyHubMethod(long userId, long tenantId)
{
_myManager.MyMethod();
}
}
The problem is that a SignalR request doesn't have a session. Therefore I have also set the required user information in the hub method as parameters postet from the client.
So I thought it is the best solution for this problem to write a new AuthorizationProvider for SignalR and adapt the depdendency resolver. But I can't get the current user in the new SignalrAuthorizationProvider.
public class SignalrAuthorizationProvider{
public long? GetUserId()
{
// How to get the user information here???
}
public long? GteTenantId() { /* and here??? */ }
}
Is there a recommended solution to this problem?
Of course, I can extend MyMethod to accept the user information as a parameter. But MyMethod calls another method from another manager and that manager also calls another method. The user information is only needed for the last method call. So I had to change at least 3 methods and many more in the future.
Here is a sketch of the problem
This is a potential solution. But it's very bad
Session is not supported by SignalR by default and you should avoid using it. See No access to the Session information through SignalR Hub. Is my design is wrong?. But you still can use cookie or querystring to get the desired value.
In both case you need to have access to the HubCallerContext of the underlying hub, the one that is accessible through the Context property of the Hub.
In a ideal word you should just have to had the dependency to the SignalAuthorizationProvider
ie :
public class SignalrAuthorizationProvider {
public SignalrAuthorizationProvider(HubCallerContext context){
this._context = context;
}
private readonly HubCallerContext _context;
public long? GetUserId() {
return this._context.Request.QueryString["UserId"]
}
}
But due to SignalR design it is not possible. Context property is assigned after construction of the Hub and AFAIK there is no way to change it.
Source code here : HubDispatcher.cs
One possible solution would be to inject a mutable dependency inside the Hub and alter the object in the OnConnected, OnReconnected methods.
public class SignalrAuthorizationProvider : IAuthorizationProvider
{
private Boolean _isInitialized;
private String _userId;
public String UserId
{
get
{
if (!_isInitialized)
{
throw new Exception("SignalR hack not initialized");
}
return this._userId;
}
}
public void OnConnected(HubCallerContext context)
{
this.Initialize(context);
}
public void OnReconnected(HubCallerContext context)
{
this.Initialize(context);
}
private void Initialize(HubCallerContext context) {
this._userId = context.QueryString["UserId"];
this._isInitialized = true;
}
}
and the Hub
public abstract class CustomHub : Hub
{
public CustomHub(IAuthorizationProvider authorizationProvider)
{
this._authorizationProvider = authorizationProvider;
}
private readonly IAuthorizationProvider _authorizationProvider;
public override Task OnConnected()
{
this._authorizationProvider.OnConnected(this.Context);
return base.OnConnected();
}
public override Task OnReconnected()
{
this._authorizationProvider.OnReconnected(this.Context);
return base.OnReconnected();
}
}
Having a mutable dependency is not the best design but I can't see any other way to have access to IRequest or HubCallerContext.
Instead of having an abstract Hub class which is not a perfect solution. You can change the RegisterHubs autofac method to use AOP with Castle.Core and let the interceptor calls the methods for you.
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.
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.
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;