Access SignalR Hub without Constructor Injection

2019-06-02 10:31发布

问题:

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.

回答1:

No It's not possible. See "official" answer from david fowler https://github.com/aspnet/SignalR/issues/1831#issuecomment-378285819

How to inject your hubContext:

Best solution is to inject your hubcontext like IHubContext<TheHubWhichYouNeedThere> hubcontext into the constructor.

See for more details:

Call SignalR Core Hub method from Controller



回答2:

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);
  }
}