17

I have a hub with method that is called client-side. This method launches a timer with a delegate that runs every 10 seconds. Since it wouldn't make sense to keep running this delegate if no one is connected to the hub, I want to check if any users are still connected from inside the delegate before I reschedule it. Is there any way to do this?

edobry
  • 335
  • 1
  • 5
  • 12

4 Answers4

20

Probably the most used solution is to keep a static variable containing users currently connected and overriding OnConnect and OnDisconnect or implementing IDisconnect depending on the version that you use.

You would implement something like this:

public class MyHub : Hub
{
    private static List<string> users = new List<string>();
    public override Task OnConnected()
    {
        users.Add(Context.ConnectionId);
        return base.OnConnected();
    }

    //SignalR Verions 1 Signature
    public override Task OnDisconnected()
    {
        users.Remove(Context.ConnectionId);
        return base.OnDisconnected();
    }

    //SignalR Version 2 Signature
    public override Task OnDisconnected(bool stopCalled)
    {
        users.Remove(Context.ConnectionId);
        return base.OnDisconnected(stopCalled);
    }

    // In your delegate check the count of users in your list.
}
VGs
  • 113
  • 7
cillierscharl
  • 7,043
  • 3
  • 29
  • 47
  • I saw this mentioned in other places, was just hoping there was a better way. Thanks, I'm going to try it! – edobry Dec 03 '12 at 21:54
  • 5
    Are there any issues with concurrency with multiple threads trying to access the single resource? – Mr Bell Nov 18 '13 at 23:39
  • 14
    Not only will this not work reliably because the lifetime model of a hub, it breaks the possibility of scaling this out with a backplane. The only real answer is to track the online status in a shared cache like redis or appfabric. – Bon Jun 25 '15 at 18:32
  • 1
    This answer is incorrect. Connection could be dead well before appropriate even is raised on timeout. – Egor Pavlikhin Jun 10 '19 at 13:40
19

If you save your connectionId in your database, you can check this:

var heartBeat = GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>();

var connectionAlive = heartBeat.GetConnections().FirstOrDefault(c=>c.ConnectionId == connection.ConnectionId);

if (connectionAlive.IsAlive)
{
//Do whatever...
}
chemitaxis
  • 13,889
  • 17
  • 74
  • 125
  • 1
    You need to add `using Microsoft.AspNet.SignalR.Transports` – Adam Szabo Apr 21 '16 at 13:53
  • Also should not that .Transports is within the Core Assembly... bummer! See: https://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.transports.transportmanager%28v=vs.118%29.aspx?f=255&MSPPError=-2147217396 – Anthony Griggs Jan 08 '18 at 13:34
  • How can I make this code work in ASPNET core 3.1 Application. I am getting error saying " ```The name 'GlobalHost' does not exist in the current context ```"; – MADMAX Mar 12 '21 at 06:40
  • @zombie551 I guess you have to hook every single HB as suggested here: https://github.com/dotnet/aspnetcore/issues/18763 – Stefano Mar 24 '21 at 14:26
1

From http://forums.asp.net/t/1829432.aspx/1?How+do+I+get+list+of+connected+clients+on+signalr+

IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.notify("Hello world");

So you should be able to get context.Clients.Count.

That post also references the wiki which has lots of good info. You could try using the OnConnected/OnDisconnected examples to track the users, and when you get to zero users stop your call.

Tim Hobbs
  • 2,017
  • 17
  • 24
  • Thanks for the suggestion, but I'm trying to do this from within a hub... will it still work? – edobry Dec 03 '12 at 21:55
1

I have done this way:

public class PrincipalCommunicator
    {
        public readonly static Lazy<PrincipalCommunicator> _instance = new Lazy<PrincipalCommunicator>(
                () => new PrincipalCommunicator(GlobalHost.ConnectionManager.GetHubContext<PrincipalHub>())
            );

        public List<string> ConnectedUsers { get; set; }

        private IHubContext _context;

        private PrincipalCommunicator(IHubContext context)
        {
            ConnectedUsers = new List<string>();
            _context = context;
        }

        public static PrincipalCommunicatorInstance
        {
            get
            {
                return _instance.Value;
            }
        }

        public bool IsUserConnected(string user)
        {
            return UsuariosConectados.Contains(user);
        }
    }

    public class PrincipalHub : Hub
    {
        public override Task OnConnected()
        {
            PrincipalComunicador.Instance.UsuariosConectados.Add(Context.User.Identity.Name);

            return base.OnConnected();
        }

        public override Task OnDisconnected(bool stopCalled)
        {
            PrincipalComunicador.Instance.UsuariosConectados.Remove(Context.User.Identity.Name);
            return base.OnDisconnected(stopCalled);
        }
    }
}

This way, all logic to send something to client stays in one place, like the example and you can know if a user is connected anywhere in the code.

VGs
  • 113
  • 7