SignalR: sub-classing my hub breaks outside calls

218 views Asked by At

I'm starting from a functioning SignalR web application with an ActivityHub class derived from a SignalR Hub to manage client connections and activities. Similar to the stock ticker tutorial, there is also a singleton ActivityTimer class that uses a System.Threading.Timer to periodically broadcast to all clients via the hub context it gets in its constructor, like this:

activityHubContext = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();

Now I want to turn my ActivityHub into a base class with sub-classes for different kinds of activities, overriding a few methods in ActivityHub for activity-specific behaviors, and using activity-specific clients which each reference the appropriate activity sub-class (e.g., var activityHub = $.connection.coreActivityHub).

The sub-classing works for the hub server code and clients, and the ActivityTimer fires timer events as intended, but the ActivityTimer calls no longer reach the clients. If I get the hub context for a specific activity sub-class, it works again, but only for that sub-class:

activityHubContext = GlobalHost.ConnectionManager.GetHubContext<CoreActivityHub>();

Is there a way to have a single, generic ActivityTimer that will work with all sub-classes of ActivityHub? Can the ActivityTimer call some method in the base ActivityHub class rather than trying to reach all the clients directly (the base class seems to have no problems calling Clients.All.doSomething())?

In case it simplifies things (or makes possible an otherwise challenging solution), the application will only be running one type of activity at a time -- all clients will be on the same activity at one time.

1

There are 1 answers

0
GISmatters On BEST ANSWER

In working on a different issue in the same project, I came across this, which points to this, where I also found this (all worth a quick read if the topic interests you). They provide one way to do what I was trying to do: have a method in the base class that can be called from "outside" to reach clients of any/all sub-classes. (They also helped me to think more clearly about the hub context, and why I believe my original ActivityTimer cannot work with sub-classes -- see note at the end of this answer for further explanation).

The solution to my problem is to create a method in the base class that does the call to the clients, and then call this new method from the ActivityTimer to reach the clients indirectly. This approach does not rely on having a hub context within ActivityTimer, and it frees us from worry about sub-classes because it calls into the base class explicitly:

  1. Create a static field in the base class to hold the base class's hub context:

    private static IHubContext thisHubContext;
    
  2. Set this hub context in each sub-class's constructor with that class as the type passed to GetHubContext():

    thisHubContext = 
        GlobalHost.ConnectionManager.GetHubContext<CoreActivityHub>();
    
  3. Create a static method in the base class that calls the desired client-side method(s); note that you could use other options than Clients.All to reach a subset of clients (for example, the arg might designate a SignalR group to reach):

    public static void DoSomething(string someArg)
    {
        thisHubContext.Clients.All.doSomething(someArg);
    }
    
  4. Call this base-class method from any server code that is "outside" the hub. In my case, I call it from the timer event handler in ActivityTimer:

    ActivityHub.DoSomething("foo");
    

The messages will get through to the clients specified in the static method.

NB: this solution only works for the particular case mentioned at the end of the original post, in which only one sub-class is ever in use at a time, because each sub-class sets the base class static hub context to its own context. I have not yet tried to find a way around this limitation.

Note: I don't think it's possible to have "outside-the-hub" server code work with sub-classes by way of a stored hub context. In my original, functioning app (before I tried to create sub-classes of the ActivityHub), the ActivityTimer talks to the clients by means of a hub context that it gets on instantiation:

public ActivityTimer()
{
    activityHubContext = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
    activityTimer = new Timer(DoSomething, null, TimerInterval, TimerInterval);
}

public void DoSomething(object state)
{
    activityHubContext.Clients.All.doSomething("foo");
}

Because the hub context is obtained by explicit reference to a particular class (in this case, ActivityHub), it will not work with a sub-class. If instead (as I mentioned trying in my original post) I get the hub context for a particular sub-class, the timer will now work for instances of that sub-class, but not other sub-classes; again, the problem is that the hub context is obtained for a particular sub-class.

I don't think there's any way around this, so I think the only solution is the one outlined above in which the base class method uses the hub context set by the sub-class constructor, and the outside code calls the base class method to get to the clients by way of that sub-class context.

However, I'm still on the SignalR learning curve (among others) so will appreciate any corrections or clarifications!