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:
Create a static field in the base class to hold the base class's hub context:
private static IHubContext thisHubContext;
Set this hub context in each sub-class's constructor with that class as the type passed to GetHubContext():
thisHubContext =
GlobalHost.ConnectionManager.GetHubContext<CoreActivityHub>();
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);
}
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!