4

I'm trying to inject a SignalR IHubContext into a Web API 2.x controller in an ASP.NET MVC 5 app Framework 4.72 (not .NET Core). It's throwing this exception when calling the Web API controller MyController:

An error occurred when trying to create a controller of type 'MyController'. Make sure that the controller has a parameterless public constructor

The inner exception says:

None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'MyController' can be invoked with the available services and parameters: Cannot resolve parameter 'Microsoft.AspNet.SignalR.IHubContext[MyHub] context' of constructor 'Void .ctor(Microsoft.AspNet.SignalR.IHubContext [MyHub])'.

I don't mind doing this using property injection but haven't had any luck getting that to work. So I'm doing injection into the c'tor of the controller.

I've followed these answers for help:

Here's the Web API controller:

public class MyController : WebApiController
{
    public IHubContext<MyHub> Context { get; set; }

    public MyController(IHubContext<MyHub> context)
    {
        Context = context;
    }   
}

And here's the pertinent part of the Startup.cs:

public void Configuration(IAppBuilder app)
{
    // Other code...

    var builder = new ContainerBuilder();
    var config = new HttpConfiguration();

    builder.RegisterHubs(Assembly.GetExecutingAssembly());
    builder.RegisterControllers(typeof(MvcApplication).Assembly)
        .InstancePerRequest();
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly())
        .InstancePerRequest();
    builder.RegisterType<AutofacDependencyResolver>()
        .As<IDependencyResolver>()
        .SingleInstance();

    builder
        .Register(c => c.Resolve<IConnectionManager>().GetHubContext<MyHub>())
        .Named<IHubContext>("MyHub");
    builder.RegisterType<MyController>()
        .WithParameter(
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == typeof(IHubContext),
                (pi, ctx) => ctx.ResolveNamed<IHubContext>("MyHub")
            )
        );

    var container = builder.Build();
    app.UseAutofacMiddleware(container);

    DependencyResolver.SetResolver(new Autofac.Integration.Mvc.AutofacDependencyResolver(container));
    config.DependencyResolver = new AutofacWebApiDependencyResolver((IContainer)container);

    app.Map("/signalr", map =>
    {
        var hubConfiguration = new HubConfiguration
        {
            Resolver = new AutofacDependencyResolver(container),
        };

        map.RunSignalR(hubConfiguration);
    });
}

What am I missing? Thanks.

Alex
  • 34,699
  • 13
  • 75
  • 158

1 Answers1

2

Your first problem is that typeof(IHubContext) is not the same as typeof(IHubContext<MyHub>). You can get around that by using:

pi.ParameterType == typeof(IHubContext).MakeGenericType(typeof(MyHub))

However, old versions of SignalR don't support the generic interfaces very well, so it would probably work better if you left the comparison as is, and inject an IHubContext rather than an IHubContext<MyHub> in MyController.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • Thanks, Bradley. An issue: The context passed into the controller doesn't find a connection ID passed to an action on the controller. In other words, when I call `Context.Clients.Client(connectionId).doSomething();`, it does nothing on the client. As if the context injected and the one the page is using are different. – Alex May 18 '20 at 18:38
  • That is a limitation of `IHubContext`: it doesn't know about the caller (https://stackoverflow.com/a/50394456/177416). A way around it: using groups to send the message down. Use `OnConnected` to add the username to a group of one; then in the Web API method, use the same username to call a client function on that group – Alex May 19 '20 at 00:25
  • Found solution: moved the web api action logic to a method on the hub. Now calling that and all is well! – Alex May 19 '20 at 15:59
  • I'm gonna share a production solution I used. When the Client makes the HubConnection have it supply a UUID or Key as part of the payload then use that for the groupId. In the end we didn't want to go all in on a backplane so instead we had all client tablets submit the hardwareId and filtered the messages client side. The advantage of this over groups was if a tablet went offline, a new tablet could use the same UUID and pickup where the old tablet left off. – jjhayter May 25 '22 at 21:04