SignalR + Autofac + OWIN: Why doesn't GlobalHost.ConnectionManager.GetHubContext work?
Asked Answered
R

3

31

I'm trying to use OWIN, SignalR and Autofac in a single project.

I'm setting things up with regards to signalR as follows:

       // Create the AutoFac container builder:
        var builder = new ContainerBuilder();

        // ...[Register various other things in here]...

        // register signalR Hubs
        builder.RegisterHubs(Assembly.GetExecutingAssembly());

        // Build the container:
        var container = builder.Build();

        // Configure SignalR with the dependency resolver.
        app.MapSignalR(new HubConfiguration
        {
            Resolver =  new AutofacDependencyResolver(container)
        });

My issue is that when I use the Autofac SignalR integration, I can no longer get a signalR Hub Context on the server (in a webapi controller for example) and so can't send messages from server side to the connected clients. Something like the following is how I do this when I'm not using the Autofac signalR integration:

var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.All.notification("Test Message");

But this doesn't work when I add Autofac into the mix - I don't get any error message and I do seem to get a hubContext, but calls on it don't actually seem to get to the clients.

If I comment out the use of the dependency resolver for signalR in the call to MapSignalR, the call to GetHubContext works again and messages reach the signalR clients sucessfully, but of course I can no longer use IoC on my hubs. e.g.

        // Configure SignalR with the dependency resolver.
        app.MapSignalR(new HubConfiguration
        {
            // Resolver =  new AutofacDependencyResolver(container)
        });

Can anybody tell me why using the AutofacDependencyResolver stops GlobalHost.ConnectionManager.GetHubContext from working correctly??

NOTE: One other thing I have tried is instead of using GlobalHost.ConnectionManager.GetHubContext() I tried injecting an IConnectionManager into the webapi controller from which I want to send a message to clients, then calling GetHubContext on that, but Autofac couldn't resolve the IConnectionManager.

I did find the following article by Piotr Szmyd which apparently allows this:

http://www.szmyd.com.pl/blog/wiring-signalr-with-autofac

but this appears to be based on obsolete signalR builds, and while there seems to be a nuget package for it here:

http://www.nuget.org/packages/SignalR.AutoFac/

it also seems well out of date.

Romish answered 15/1, 2014 at 0:2 Comment(1)
these are starting to feel like duplicatesDemocratic
F
33

If you use a custom dependency resolver with SignalR, you can no longer use GlobalHost unless you modify it:

GlobalHost.DependencyResolver = new AutofacDependencyResolver(container);
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();

// A custom HubConfiguration is now unnecessary, since MapSignalR will
// use the resolver from GlobalHost by default.
app.MapSignalR();

If you don't want to modify GlobalHost, you will have to manually resolve your IConnectionManager:

IDependencyResolver resolver = new AutofacDependencyResolver(container);
IHubContext hubContext = resolver.Resolve<IConnectionManager>().GetHubContext<MyHub>();

app.MapSignalR(new HubConfiguration
{
    Resolver = resolver
});
Faeroese answered 15/1, 2014 at 0:22 Comment(6)
Aha! Thank you I think this is getting me closer to an answer. Are there any disadvantages to modifying GlobalHost? (for example if I want to switch to self host in the future,etc?)Romish
The only disadvantage to modifying GlobalHost that I can think of is that you are relying on global state. If you want to run multiple instances of SignalR in the same process, then GlobalHost probably isn't for you. Otherwise it's probably fine.Faeroese
Hi, in the controller how can i get THE INSTANCE of the HUB? Please!Juridical
There is no single instance of a SignalR Hub, since Hubs are ephemeral. It doesn't make much sense to retrieve a Hub instance from a global context, because Hub.Context relies a bunch of request data and things like Hub.Clients.Caller make no sense outside SignalR's hub invocation pipeline. Is there a specific reason you need a hub instance? Maybe there is an alternative solution.Faeroese
For future readers, the alternative solution for oagostinho would be to add the signalr c# client to the mvc/webapi controller in order to make the call to push data.Ethe
@Faeroese Great answer! The only question I have is: which of them is considered the better practice; Does this not violate this from the official documentation: "A common error in OWIN integration is use of the GlobalHost. In OWIN you create the configuration from scratch. You should not reference GlobalHost anywhere when using the OWIN integration." ?Miffy
R
2

For a complete answer, with SignalR, Autofac, and OWIN, I did the following:

// register IPersistantConnectionContext for access to SignalR connection
// and IDependencyResolver to enable inection of the container into its
// construction for the config object.
var builder = new ContainerBuilder();
builder.RegisterType<Autofac.Integration.SignalR.AutofacDependencyResolver>()
    .As<IDependencyResolver>()
    .SingleInstance();
builder.Register((context, p) =>
        context.Resolve<IDependencyResolver>()
            .Resolve<Microsoft.AspNet.SignalR.Infrastructure.IConnectionManager>()
            .GetConnectionContext<SignalRConnection>());

// ... other registrations

var container = builder.Build();

var signalrConfiguration = new ConnectionConfiguration
{
    Resolver = container.Resolve<IDependencyResolver>(),
};

app.UseAutofacMiddleware(container);

app.MapSignalR<SignalRConnection>("/signalr", signalrConfiguration);

// ... other middleware

In my controllers, I included a parameter of the type IPersistentConnectionContext and the correct instance is injected.

I was using a PersistentConnection, but it should be similar for Hubs.

Recovery answered 17/12, 2015 at 22:34 Comment(1)
Thanks this indeed works! Here is a complete sample https://mcmap.net/q/471610/-how-to-configure-autofac-and-signalr-in-a-mvc-5-application using hubsIsobath
N
-1

To expand on Nathan's answer, I am using something similar with the key line being

builder.RegisterHubs(Assembly.GetExecutingAssembly()).SingleInstance();

in Startup.cs.

The line "SingleInstance()" ensures that only a single "instance" of the hub is used throughout the application.

Then I just use straightforward dependency injection into the constructor of the controller to get a pointer to the hub.

Neolithic answered 29/8, 2016 at 15:28 Comment(1)
I'm not sure that a 'single instance' of a hub makes sense, as a Hub is created on the fly, with request context etc.Springtail

© 2022 - 2024 — McMap. All rights reserved.