Search code examples
asp.net-core-2.0asp.net-core-signalr

Access SignalR Hub without Constructor Injection


With AspNetCore.SignalR (1.0.0 preview1-final) and AspNetCore.All (2.0.6), how can I invoke a method on a hub in server code that is not directly in a Controller and is in a class that cannot be made via Dependency Injection?

Most examples assume the server code is in a Controller and should 'ask' for the hub via an injectable parameter in a class that will created by DI.

I want to be able to call the hub's method from server code at any time, in code that is not injected. The old SignalR had a GlobalHost that enabled this approach. Basically, I need the hub to be a global singleton.

Now, everything seems to be dependent on using Dependency Injection, which is introducing a dependency that I don't want!

I've seen this request voiced in a number of places, but haven't found a working solution.

Edit

To be more clear, all I need is to be able to later access the hubs that I've registered in the Configure routine of the Startup class:

app.UseSignalR(routes =>
        {
            routes.MapHub<PublicHubCore>("/public");
            routes.MapHub<AnalyzeHubCore>("/analyze");
            routes.MapHub<ImportHubCore>("/import");
            routes.MapHub<MainHubCore>("/main");
            routes.MapHub<FrontDeskHubCore>("/frontdesk");
            routes.MapHub<RollCallHubCore>("/rollcall");
            // etc.
            // etc.
        }); 

If I register them like this:

 services.AddSingleton<IPublicHub, PublicHubCore>();

it doesn't work, since I get back an uninitiated Hub.


Solution

  • Thanks to those who helped with this. Here's what I've ended up on for now...

    In my project, I can call something like this from anywhere:

    Startup.GetService<IMyHubHelper>().SendOutAlert(2);
    

    To make this work, I have these extra lines in Startup.cs to give me easy access to the dependency injection service provider (unrelated to SignalR):

    public static IServiceProvider ServiceProvider { get; private set; }
    public static T GetService<T>() { return ServiceProvider.GetRequiredService<T>(); }
    public void Configure(IServiceProvider serviceProvider){
      ServiceProvider = serviceProvider;
    }
    

    The normal SignalR setup calls for:

    public void Configure(IApplicationBuilder app){
      // merge with existing Configure routine
      app.UseSignalR(routes =>
      {
        routes.MapHub<MyHub>("/myHub");
      });
    }
    

    I don't want all my code to have to invoke the raw SignalR methods directly so I make a helper class for each. I register that helper in the DI container:

    public void ConfigureServices(IServiceCollection services){
      services.AddSingleton<IMyHubHelper, MyHubHelper>();
    }
    

    Here's how I made the MyHub set of classes:

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    public class MyHub : Hub { }
    
    public interface IMyHubHelper
    {
      void SendOutAlert(int alertNumber);
    }
    
    public class MyHubHelper : IMyHubHelper
    {
      public IHubContext<MyHub> HubContext { get; }
      public MyHubHelper(IHubContext<MyHub> hubContext) 
      {
        HubContext = hubContext;
      }
    
      public void SendOutAlert(int alertNumber)
      {
        // do anything you want to do here, this is just an example
        var msg = Startup.GetService<IAlertGenerator>(alertNumber)
    
        HubContext.Clients.All.SendAsync("serverAlert", alertNumber, msg);
      }
    }