Injecting scoped dependencies into ASP.NET Signalr Hub

May 2, 2014 at 6:44 AM
Edited May 2, 2014 at 6:45 AM
Recently I had a need to inject my signalr hubs which requires scoped dependencies (lifetime scope or web scope). But obviously injection fails because of following reasons:
  1. SignalR runs on katana (Microsoft OWIN impl) which is highly async
  2. Lifetime scope is designed only for single threading
  3. There was no explicit way to start the scope and dispose it in signalr pipeline
So, for those of you who are interested I have a working solution.

a. All of my scoped dependencies use hybrid lifestyle which either uses WebRequestLifestyle or ExecutionContextScopeLifestyle based on following piece of code
Lifestyle.CreateHybrid(() => System.Web.HttpContext.Current != null && SimpleInjector.Extensions.ExecutionContextScoping.SimpleInjectorExecutionContextScopeExtensions.GetCurrentExecutionContextScope(Container) == null,
                new WebRequestLifestyle(),
                new SimpleInjector.Extensions.ExecutionContextScoping.ExecutionContextScopeLifestyle()
            );
b. Use IHubActivator to inject dependencies and also start the ExecutionContextScope
class HubActivator : IHubActivator
        {
            public IHub Create(HubDescriptor descriptor)
            {
                var dispose = true;
                Scope scope = null;
                try {
                    scope = Container.BeginExecutionContextScope();

                    var hub = Container.GetInstance(descriptor.HubType);

                    var baseHub = hub as BaseHub;
                    if (baseHub != null) {
                        baseHub.Scope = scope;
                        dispose = false;
                    }

                    return (IHub)hub;
                } finally {
                    if (dispose && scope != null) {
                        scope.Dispose();
                    }
                }
            }
        }
c. Create base hub
public class BaseHub : Hub
    {
        internal Scope Scope { get; set; }

        protected override void Dispose(bool disposing)
        {
            if (disposing && Scope != null) {
                Scope.Dispose();
            }

            base.Dispose(disposing);
        }
    }
Because of ExecutionContextScope flows with async/await methods, this solution seems to be working.

If you have any other ideas/flaws you can see, then feel free to post

Thanks
Coordinator
May 2, 2014 at 1:22 PM
Based on Joshua's idea, here's a related implementation, that uses a IHub decorator instead of a Hub base class. This prevents you from having to derive from a base class:
using SimpleInjector;
using SimpleInjector.Extensions.ExecutionContextScoping;

public sealed class SimpleInjectorScopedHubActivator : IHubActivator
{
    private readonly Container container;

    public SimpleInjectorScopedHubActivator(Container container) {
        this.container = container;
    }

    public IHub Create(HubDescriptor descriptor) {
        Scope scope = null;

        try {
            scope = this.container.BeginExecutionContextScope();

            return new ScopedHubDecorator(
                (IHub)this.container.GetInstance(descriptor.HubType),
                scope);
        }
        catch {
            if (scope != null) {
                scope.Dispose();
            }

            throw;
        }
    }

    private sealed class ScopedHubDecorator : IHub
    {
        private readonly IHub decoratee;
        private readonly Scope scope;

        public ScopedHubDecorator(IHub decoratee, Scope scope) {
            this.decoratee = decoratee;
            this.scope = scope;
        }

        public IHubCallerConnectionContext Clients {
            get { return this.decoratee.Clients; }
            set { this.decoratee.Clients = value; }
        }

        public HubCallerContext Context {
            get { return this.decoratee.Context; }
            set { this.decoratee.Context = value; }
        }

        public HubCallerContext Context {
            get { return this.decoratee.Context; }
            set { this.decoratee.Context = value; }
        }

        public Task OnConnected() {
            return this.decoratee.OnConnected();
        }

        public Task OnDisconnected() {
            return this.decoratee.OnDisconnected();
        }

        public Task OnReconnected() {
            return this.decoratee.OnReconnected();
        }

        public void Dispose() {
            try {
                this.decoratee.Dispose();
            }
            finally {
                this.scope.Dispose();
            }
        }
    }
}
Coordinator
May 2, 2014 at 1:26 PM
About the hybrid lifestyle, I think it's safer to simply check whether there is an active ExecutionContextScope, since there could be an active scope within a Web Request as well, but in that case you would probably still prefer to have your instances scoped using the ExecutionContextScope.

The creation of the hybrid lifestyle can be shortened to the following:
using SimpleInjector;
using SimpleInjector.Integration.Web;
using SimpleInjector.Extensions.ExecutionContextScoping;

var hybridLifestyle = Lifestyle.CreateHybrid(
    () => container.GetCurrentExecutionContextScope() != null,
    new ExecutionContextScopeLifestyle(),
    new WebRequestLifestyle());
May 2, 2014 at 5:25 PM
I do not know if using hub decorator will affect hub behavior. What if hub methods are decorated with Authorize or some other attributes.

But I think it is safe (did not test), but IHubActivator is only used to create instances (not for creating instances and reflecting on meta data).

Great idea, I hope it helps who are interested.
Coordinator
May 2, 2014 at 10:51 PM
I must admit that I have no experience with SignalR myself, but it would be a severe design flaw if the framework would need that. But actually Microsoft's new Web API framework contains this exact flaw, because it is impossible to decorate any IHttpController instances, since Web API requires the exact requested controller type. This is of course quite bizarre, because hat practicalpy makes the IHttpController interface useless, and MVC on the other hand does respect their IController interface.

But I wouldn't be suprised if SignalR can't handle this, but you need to test this. Please post it here if it works (or doesn't work). That would help others as well.
Aug 24, 2014 at 1:08 PM
Edited Aug 24, 2014 at 1:09 PM
The Hub Decorator did not work for me, but the BaseHub. My SignalR JavaScript Client called a method on the server and SignalR could not invoke the server method.

I used SignalR 2.1.0.0.
Oct 20, 2014 at 4:33 AM
Instead of passing the scope onto the hubs, im using the following:
    public class SimpleInjectorHubScopeManager : IScopeManager
    {
        readonly ConcurrentDictionary<IHub, Scope> _hubLifetimeScopes =
            new ConcurrentDictionary<IHub, Scope>();
        readonly Container _rootScope;

        public SimpleInjectorHubScopeManager(Container rootScope) {
            _rootScope = rootScope;
        }

        public T CreateHub<T>(Type hubType) where T : InternalBaseHub {
            var scope = _rootScope.BeginExecutionContextScope();
            var hub = (T) _rootScope.GetInstance(hubType);
            hub.Disposing += HubOnDisposing;
            _hubLifetimeScopes.TryAdd(hub, scope);
            return hub;
        }

        void HubOnDisposing(object sender, EventArgs eventArgs) {
            var hub = sender as InternalBaseHub;
            if (hub == null)
                return;
            hub.Disposing -= HubOnDisposing;
            Scope scope;
            if (!_hubLifetimeScopes.TryRemove(hub, out scope))
                return;
            scope.Dispose();
        }
    }
with on the InternalBaseHub:
...
    protected override void Dispose(bool disposing) {
        if (_disposed)
            return;
        _disposed = true;

        base.Dispose(disposing);
        if (!disposing)
            return;

        var handler = Disposing;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }
...

Not sure that's cleaner :/
Oct 20, 2014 at 12:02 PM
Can it be that all of the given solutions are not possible to use when you have an async hub method?

I am using the approach from joshua0420 but the problem is that the hub is disposed before the async hub method is completely executed.

Any ideas?