Proper Hub dependency lifetime management for SignalR and Castle Windsor

I’ve had a bit similar problem but with Unity instead of Castle Windsor.

My requirements:

  • I wanted to avoid singleton registrations on the container.
  • All objects are resolved in Hub and should be disposed on Hub destruction.
  • Registrations reused across Web Api and SignalR.
  • Object lifetime is managed by HierarchicalLifetimeManager – child containers resolve and manage separate object instances. Registered like this:
container.RegisterType<IMessageService, MessageService>(new HierarchicalLifetimeManager());

This is my solution:

[HubName("exampleHub")]
public class ExampleHub : Hub
{
    IUnityContainer _container;

    public CarrierApiHub(IUnityContainer container) // container itself injected in hub
    {
        _container = container.CreateChildContainer(); // child container derived from the main container.
    }

    public async Task<int> UnreadMessagesCount()
    {
        // Here i'm resolving instance of IMessageService which depends on
        // other registrations specified on the container. Full object graph
        // is constructed and destroyed on hub disposal.
        var messageSvc = _container.Resolve<IMessageService>();
        return await messageSvc.CountUnreadOf(UserId);
    }

    protected override void Dispose(bool disposing)
    {
        _container.Dispose(); // child container destroyed. all resolved objects disposed.
        base.Dispose(disposing);
    }

    private int UserId
    {
        get
        {
            // only an example
            var claim = ((ClaimsPrincipal)Context.User).GetClaim("user_id");
            return int.Parse(claim.Value);
        }
    }
}

SignalR and dependency resolver configuration:

public static class ConfigureSignalR
{
    public static void Initialize(UnityContainer unityContainer, IAppBuilder app)
    {
        app.Map("/signalr", map =>
        {
            var resolver = new AppSignalRDependencyResolver(unityContainer);

            map.UseCors(CorsOptions.AllowAll);

            var hubConfiguration = new HubConfiguration
            {
                EnableJavaScriptProxies = false,
                EnableJSONP = true, // Required for IE 9 (supports only polling)
                Resolver = resolver
            };

            map.RunSignalR(hubConfiguration);
        });
    }
}

Dependency resolver implementation:

public class AppSignalRDependencyResolver : DefaultDependencyResolver
{
    protected IUnityContainer _container;

    public AppSignalRDependencyResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this._container = container.CreateChildContainer();
    }

    public override object GetService(Type serviceType)
    {
        try
        {
            return _container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return base.GetService(serviceType);
        }
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return _container.ResolveAll(serviceType).Concat(base.GetServices(serviceType));
        }
        catch (ResolutionFailedException)
        {
            return base.GetServices(serviceType);
        }
    }

    protected override void Dispose(bool disposing)
    {
        _container.Dispose();
        base.Dispose(disposing);
    }
}

Leave a Comment

tech