2

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.

Glen Little
  • 6,951
  • 4
  • 46
  • 68
  • Your usage scenario is cutting against the grain of both the new SignalR & ASP.NET Core design but if you must support this scenario, [look at this truly evil code hack](https://stackoverflow.com/a/40029302) for an understanding of what you are up against. Maybe if you described your specific case to "call the hub's method from server code at any time" then there could be other approaches to solving that problem. – Sixto Saez Apr 03 '18 at 18:04
  • Actually tried that "evil hack" but it doesn't work with SignalR as the hubs need to be initialized correctly. – Glen Little Apr 03 '18 at 21:02

3 Answers3

2

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

Stephu
  • 3,236
  • 4
  • 21
  • 33
  • My whole point though is that in some classes where I need it, the class is not created via DI. How can I get it there? Or do I have to make all the parent classes pass down the reference to the hub from the Controller? That introduces too many dependencies! – Glen Little Apr 03 '18 at 21:13
  • @GlenLittle Ok than your question is more a DI Question. That's difficult to give you a general answer. There are a lot opinions about clean design. In one project we created a DI Factory as singleton which gives us the required instances. Other solution is (as you wrote) to pass all the required objects. But in the case you have to pass to much things I would think again about your design. – Stephu Apr 03 '18 at 21:21
  • the idea of a DI factory would be okay for me, but it doesn't work with Hubs... or, I don't know how to connect to the Hubs' DI factory. – Glen Little Apr 03 '18 at 21:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/168147/discussion-between-glen-little-and-stephu). – Glen Little Apr 03 '18 at 21:41
1

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);
  }
}
Glen Little
  • 6,951
  • 4
  • 46
  • 68
  • Your questions was "Access SignalR Hub without Injection" --> The answer is no (as we discussed) --> but now in your answer you do it with injection. This is not the answer of your question. – Stephu Apr 09 '18 at 10:04
  • @Stephu You are correct, but it was constructor injection that is the roadblock. In my solution, the host code does not need to have a constructor that relies on injection. My question specifically says: "... in a class that cannot be made via Dependency Injection" – Glen Little Apr 10 '18 at 15:37
  • I've updated the title to more closely match the question. – Glen Little Apr 14 '18 at 04:41
1

This is a nice solution. In .NET Core 2.1 the service provider is disposed and you get cannot access disposed object. The fix is to create a scope:

   public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider.CreateScope().ServiceProvider;
Paul
  • 41
  • 3