Saturday, January 11, 2014

Dependency Injection with SignalR and MVC

Thank you to Isaac Abraham for bringing this solution to my attention!

Do not extend DefaultDependencyResolver

How do you practice inversion of control with SignalR? The official SignalR documentation suggests that you extend the DefaultDependencyResolver base class, and then register that as SignalRs dependency resolver.

This however, can have some nasty side effects! In my case, the ConnectionManager.GetHubContext method was not properly resolving my contexts, and thus I was unable to broadcast messages to my hubs clients.

So then, what is a less obtrusive way to inject your dependencies into your hubs?

Instead register an IHubActivator with the DependencyResolver

A simpler solution is just to register a custom HubActivator with the already existing DependencyResolver. This means that instead of completely replacing the SignalR resolver you are instead adding to it.

The HubActivator is only used when trying to resolve a hub; thus the activator can wrap your container of choice and use it to resolve your hub with its dependencies, while leaving the rest of the SignalR pipeline intact.

SignalR Startup with MVC DepenencyResolver

What does this all mean? It means that once you have your dependency injection setup for MVC, you can then create hubs with dependencies using the same container! This MvcHubActivator will use the default System.Web.Mvc.DependencyResolver to resolve its Hubs:

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Owin;
using Owin;
using SignalRTester.App_Start;
using System.Web.Mvc;
 
[assembly: OwinStartup(typeof(Startup))]
 
namespace SignalRTester.App_Start
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var unityHubActivator = new MvcHubActivator();
 
            GlobalHost.DependencyResolver.Register(
                typeof(IHubActivator), 
                () => unityHubActivator);
            
            app.MapSignalR();
        }
 
        public class MvcHubActivator : IHubActivator
        {
            public IHub Create(HubDescriptor descriptor)
            {
                return (IHub) DependencyResolver.Current
                    .GetService(descriptor.HubType);
            }
        }
    }
}
Shout it

Enjoy,
Tom

8 comments:

  1. This is one of the easiest and simplest (straight forward) way of implementing DI with SignalR. I've been through many blogs and articles regarding this, but you sir nailed it! Thank you so much for sharing. I really like the idea of registering HubActivator with the MVC DependencyResolver!

    ReplyDelete
  2. The best solution I have ever found. Thanks a lot Tom !!!

    ReplyDelete
  3. OnDisconnected method does not work.

    ReplyDelete
  4. I am using Ninject with this approach but have not been able to get it to work. I get a "Protocol error: Unknown transport." in the browser when requesting any url for the app. In the chrome console it will show it as a 400 bad request and under the hood it's a typeloadexception that gets thrown. Any suggestions on what to look at would be appreciated

    ReplyDelete
  5. I am using Ninject within MVC 5 and this works great. Other methods involving a custom resolver were not working with our SQL backplane. Thanks for posting this!

    ReplyDelete
  6. I'm trying to create a way of defining hubs in external assemblies to provide extensibility without recompiling, in other words, late binding for my hubs. The exact concrete type of these hubs would not be known to either the Configuration method or the Startup method. I tried the approach above. However, when a client attempts to create proxy based on a named hub, it seems SignalR is trying to resolve that name to a Type even before the custom hub activator is invoked. Is that custom activator too late in the pipeline?

    ReplyDelete
  7. A word of warning. This solution causes memory leaks as the hub instances never get released. I've been using this approach with Castle Windsor for some time, but only recently discovered this problem after developing one particular hub that gets called many times per second. It's been documented in a few places but it looks like there's no fix until SignalR v3 (https://github.com/SignalR/SignalR/issues/3208).

    ReplyDelete

Real Time Web Analytics