35

I thought using my own IoC would be pretty straight forward with SignalR and maybe it is; most likely I'm doing something wrong. Here's my code I have so far:

private static void InitializeContainer(Container container)
{

   container.Register<IMongoHelper<UserDocument>, MongoHelper<UserDocument>>();
   // ... registrations like about and then:
   var resolver = new SimpleInjectorResolver(container);
   GlobalHost.DependencyResolver = resolver;
}

and then my class:

public class SimpleInjectorResolver : DefaultDependencyResolver
{
    private Container _container;
    public SimpleInjectorResolver(Container container)
    {
        _container = container;
    }

    public override object GetService(Type serviceType)
    {
        return _container.GetInstance(serviceType) ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType) ?? base.GetServices(serviceType);
    }
}

What ends up happening is I get an error that IJavaScriptProxyGenerator can't be resolved, so I think, well I'll add the registration:

container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);

but then there are a bunch of others! I get to:

container.Register<IDependencyResolver, SimpleInjectorResolver>();
container.Register<IJavaScriptMinifier, NullJavaScriptMinifier>();
container.Register<IJavaScriptProxyGenerator, DefaultJavaScriptProxyGenerator>(
    ConstructorSelector.MostParameters);
container.Register<IHubManager, DefaultHubManager>();
container.Register<IHubActivator, DefaultHubActivator>();
container.Register<IParameterResolver, DefaultParameterResolver>();
container.Register<IMessageBus, InProcessMessageBus>(ConstructorSelector.MostParameters);

Which still gives me "No registration for type ITraceManager could be found." ... but now I'm wondering if I'm doing this right at all as I hoping I wouldn't need to re-wire everything SignalR is doing...right? Hopefully? If not I'll keep trudging along but I'm a SignalR and Simple Injector newb so thought I'd ask first. :)

Additional: https://cuttingedge.it/blogs/steven/pivot/entry.php?id=88 since SignalR had multiple constructors.

Steven
  • 166,672
  • 24
  • 332
  • 435
rball
  • 6,925
  • 7
  • 49
  • 77

6 Answers6

51

Well, I tried yesterday and I've found a solution. According to me, the only moment where I want dependency injection in SignalR is for my hubs: I don't care about how SignalR is working inside ! So instead of replacing the DependencyResolver, I created my own implementation of IHubActivator :

public class SimpleInjectorHubActivator : IHubActivator
{
    private readonly Container _container;

    public SimpleInjectorHubActivator(Container container)
    {
        _container = container;
    }

    public IHub Create(HubDescriptor descriptor)
    {
        return (IHub)_container.GetInstance(descriptor.HubType);
    }
}

That I can register like this (in Application_Start) :

var activator = new SimpleInjectorHubActivator(container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
RouteTable.Routes.MapHubs();
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    I think I prefer your solution over mine (+1). – Steven Aug 28 '13 at 13:34
  • 2
    I scoured this question so many times and somehow missed this answer until I'd already found it somewhere else. More up-votes! – Facio Ratio Apr 22 '14 at 19:29
  • 2
    Even simpler implementation, passing it to MVC Dependency Resolver: `return (IHub)DependencyResolver.Current.GetService(descriptor.HubType);` – Chris Aug 12 '14 at 17:23
  • 1
    May I ask, for what you call `RouteTable.Routes.MapHubs();`? This line I don't understand. If I omit it, it works also like a charm. – ChristianMurschall May 07 '18 at 10:25
  • GlobalHost no longer exists in in AspNetCore 3 – William Jockusch Feb 13 '20 at 11:59
  • I don't know what you are trying to achieve but this answer (from 2014) is not at all useful for SignalR in AspNetCore. Unlike SignalR with ASP.Net MVC, in ASP.Net Core everything was designed for dependency injection (with the one provided by the framework). I suggest you open a new issue if you have trouble with dependency injection in SignalR with ASP.Net Core. – Nathanael Marchand Feb 14 '20 at 13:16
  • what is container.. – Seabizkit Dec 21 '22 at 11:44
26

Want to throw my 2 cents in here with the other answers, which can be helpful in finding your own way with dependency injection in SignalR, either using SimpleInjector or another IoC.

Using @Steven's answer

If you decide to use Steven's answer, make sure you register your hub routes before you compose the root. The SignalRRouteExtensions.MapHubs extension method (a.k.a. routes.MapHubs()) will call Register(Type, Func<object>) on the GlobalHost.DependencyResolver when mapping the hub routes, so if you swap out the DefaultDependencyResolver with Steven's SimpleInjectorResolver before the routes are mapped, you will run into his NotSupportedException.

Using @Nathanael Marchand's answer

This is my favorite. Why?

  1. Less code than the SimpleInjectorDependencyResolver.
  2. No need to replace the DefaultDependencyResolver (a.k.a. GlobalHost.DependencyResolver), which means even less code.
  3. You can compose the root either before or after mapping hub routes, since you are not replacing the DefaultDependencyResolver, it will "just work".

Like Nathanael said though, this is only if you care about the dependencies on your Hub classes, which will probably be the case for most. If you want to mess around with injecting other dependencies into SignalR, you might want to go with Steven's answer.

Problems with per-web-request dependencies in a Hub

There is an interesting thing about SignalR... when a client disconnects from a hub (for example by closing their browser window), it will create a new instance of the Hub class in order to invoke OnDisconnected(). When this happens, HttpContext.Current is null. So if this Hub has any dependencies that are registered per-web-request, something will probably go wrong.

In Ninject

I tried out SignalR dependency injection using Ninject and the ninject signalr dependency resolver on nuget. With this configuration, dependencies that are bound .InRequestScope() will be created transiently when injected into a Hub during a disconnect event. Since HttpContext.Current is null, I suppose Ninject just decides to ignore it and create transient instances without telling you. Maybe there was a configuration setting to tell ninject to warn about this, but it was not the default.

In SimpleInjector

SimpleInjector on the other hand will throw an exception when a Hub depends on an instance that is registered with WebRequestLifestlyle:

The registered delegate for type NameOfYourHub threw an exception. The registered delegate for type NameOfYourPerRequestDependency threw an exception. The YourProject.Namespace.NameOfYourPerRequestDependency is registered as 'PerWebRequest', but the instance is requested outside the context of a HttpContext (HttpContext.Current is null). Make sure instances using this lifestyle are not resolved during the application initialization phase and when running on a background thread. For resolving instances on background threads, try registering this instance as 'Per Lifetime Scope': https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#scoped.

...note this exception will only bubble up when HttpContext.Current == null, which as far as I can tell, only happens when SignalR requests a Hub instance in order to invoke OnDisconnected().

Solutions for per-web-request dependencies in a Hub

Note that none of these are really ideal, it will all depend on your application requirements.

In Ninject

If you need non-transient dependencies, just don't override OnDisconnected() or do anything custom there with the class dependencies. If you do, each dependency in the graph will be a separate (transient) instance.

In SimpleInjector

You need a hybrid lifestyle between WebRequestLifestlye and either Lifestyle.Transient, Lifestyle.Singleton, or LifetimeScopeLifestyle. When HttpContext.Current is not null, dependencies will only live as long as the web request as you would normally expect. However when HttpContext.Current is null, dependencies will either be injected transiently, as singletons, or within a lifetime scope.

var lifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => HttpContext.Current != null,
    trueLifestyle: new WebRequestLifestyle(),
    falseLifestyle: Lifestyle.Transient // this is what ninject does
    //falseLifestyle: Lifestyle.Singleton
    //falseLifestyle: new LifetimeScopeLifestyle()
);

More about LifetimeScopeLifestyle

In my case, I have an EntityFramework DbContext dependency. These can be tricky because they can expose problems when registered transiently or as singletons. When registered transiently, you can end up with exceptions while trying to work with entities attached to 2 or more DbContext instances. When registered as a singleton, you end up with more general exceptions (don't ever register a DbContext as a singleton). In my case I needed the DbContext to live within a specific lifetime in which the same instance can be reused across many nested operations, which means I needed the LifetimeScopeLifestyle.

Now if you used the hybrid code above with the falseLifestyle: new LifetimeScopeLifestyle() line, you will get another exception when your custom IHubActivator.Create method executes:

The registered delegate for type NameOfYourHub threw an exception. The NameOfYourLifetimeScopeDependency is registered as 'LifetimeScope', but the instance is requested outside the context of a lifetime scope. Make sure you call container.BeginLifetimeScope() first.

The way you have set up a lifetime scoped dependency goes like this:

using (simpleInjectorContainer.BeginLifetimeScope())
{
    // resolve solve dependencies here
}

Any dependencies that are registered with lifetime scope must be resolved within this using block. Furthermore, if any of those dependencies implement IDisposable, they will be disposed of at the end of the using block. Don't be tempted to do something like this:

public IHub Create(HubDescriptor descriptor)
{
    if (HttpContext.Current == null)
        _container.BeginLifetimeScope();
    return _container.GetInstance(descriptor.HubType) as IHub;
}

I asked Steven (who also happens to be the SimpleInjector author in case you didn't know) about this, and he said:

Well.. If you don’t dispose the LifetimeScope, you’ll be in big trouble, so make sure they get disposed. If you don’t dispose the scopes, they will hang around for ever in ASP.NET. This is because scopes can be nested and reference their parent scope. So a thread keeps alive the most inner scope (with its cache) and this scope keeps alive its parent scope (with its cache) and so on. ASP.NET pools threads and doesn’t reset all values when it grabs a thread from the pool, so this means that all scopes stay alive and the next time you grab a thread from the pool and start a new lifetime scope, you will simply creating a new nested scope and this will keep stacking up. Sooner or later, you’ll get an OutOfMemoryException.

You can't use IHubActivator to scope the dependencies because it does not live as long as the Hub instance it creates. So even if you wrapped the BeginLifetimeScope() method in a using block, your dependencies would be disposed immediately after the Hub instance is created. What you really need here is another layer of indirection.

What I ended up with, with much thanks to Steven's help, is a command decorator (and a query decorator). A Hub cannot depend on per-web-request instances itself, but must instead depend on another interface whose implementation depends on the per-request instances. The implementation that is injected into the Hub constructor is decorated (via simpleinjector) with a wrapper that begins and disposes of the lifetime scope.

public class CommandLifetimeScopeDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly Func<ICommandHandler<TCommand>> _handlerFactory;
    private readonly Container _container;

    public CommandLifetimeScopeDecorator(
        Func<ICommandHandler<TCommand>> handlerFactory, Container container)
    {
        _handlerFactory = handlerFactory;
        _container = container;
    }

    [DebuggerStepThrough]
    public void Handle(TCommand command)
    {
        using (_container.BeginLifetimeScope())
        {
            var handler = _handlerFactory(); // resolve scoped dependencies
            handler.Handle(command);
        }
    }
}

... it is the decorated ICommandHandler<T> instances that depend on per-web-request instances. For more information on the pattern used, read this and this.

Example registration

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), assemblies);

container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(CommandLifetimeScopeDecorator<>)
);
Cœur
  • 37,241
  • 25
  • 195
  • 267
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • that's very interesting to be honest, how do you register your dependencies using the handlerFactory? – pedrommuller Sep 11 '13 at 16:39
  • 1
    You don't need to do anything special to register dependencies to use the handlerFactory. I just register the command handlers with `RegisterManyForOpenGeneric`, then decorate them with `RegisterDecorator` passing the `CommandLifetimeScopeDecorator`. You can actually change the code above to use an `ICommandHandler` instead of a `Func>` and it will still inject. It will just inject the instance instead of a delegate to lazy inject the instance. I've updated the answer with an example. – danludwig Sep 11 '13 at 16:46
  • how I implement your lifetimeScope into a hub? – pedrommuller Sep 11 '13 at 17:26
  • 1
    All of the information is here or you can find it yourself. If it's too advanced a topic, try another solution or ask a different question. – danludwig Sep 11 '13 at 17:36
  • no problem I'll poke around I'll try to figure it out, thanks – pedrommuller Sep 11 '13 at 17:45
  • Now that SI has `ExecutionContext` lifestyles, do you still need your hybrid lifestyle? Its a good read nevertheless! – Mrchief May 12 '14 at 19:58
  • To rephrase this answer: if you are using Simple Injector and NOT using fancy Command/QueryHandlers, you simply can not inject your Repository (with dependency on DbContext, which are configured with per-web-request lifestyle) into your SignalR hub... right @Steven? – Ilja S. Aug 07 '15 at 15:50
  • @IljaS. No, you are wrong. The solution is simple: either don't use OnDisconnected or create a scope and resolve new instances during the call to OnDisconnected. But please note that this is a design flaw in SignalR that even affects Ninject users, because using transient DbContext instances in the object graph can seriously break your application. – Steven Aug 07 '15 at 17:21
  • @Steven - Then looks like Im missing something. I have described it [separate thread](http://stackoverflow.com/questions/31895630/simple-injector-per-web-api-request-dependency-in-signalr-hub) , but in short this seems like not working in Web API scenario... I would be very appreciated if you could take a look. Thanks! – Ilja S. Aug 08 '15 at 15:39
6

UPDATE This answer has been updated for SignalR version 1.0

This is how to build a SignalR IDependencyResolver for Simple Injector:

public sealed class SimpleInjectorResolver 
    : Microsoft.AspNet.SignalR.IDependencyResolver
{
    private Container container;
    private IServiceProvider provider;
    private DefaultDependencyResolver defaultResolver;

    public SimpleInjectorResolver(Container container)
    {
        this.container = container;
        this.provider = container;
        this.defaultResolver = new DefaultDependencyResolver();
    }

    [DebuggerStepThrough]
    public object GetService(Type serviceType)
    {
        // Force the creation of hub implementation to go
        // through Simple Injector without failing silently.
        if (!serviceType.IsAbstract && typeof(IHub).IsAssignableFrom(serviceType))
        {
            return this.container.GetInstance(serviceType);
        }

        return this.provider.GetService(serviceType) ?? 
            this.defaultResolver.GetService(serviceType);
    }

    [DebuggerStepThrough]
    public IEnumerable<object> GetServices(Type serviceType)
    {
        return this.container.GetAllInstances(serviceType);
    }

    public void Register(Type serviceType, IEnumerable<Func<object>> activators)
    {
        throw new NotSupportedException();
    }

    public void Register(Type serviceType, Func<object> activator)
    {
        throw new NotSupportedException();
    }

    public void Dispose()
    {
        this.defaultResolver.Dispose();
    }
}

Unfortunately, there is an issue with the design of the DefaultDependencyResolver. That's why the implementation above does not inherit from it, but wraps it. I created an issue about this on the SignalR site. You can read about it here. Although the designer agreed with me, unfortunately the issue hasn't been fixed in version 1.0.

I hope this helps.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Check out this link: https://github.com/SignalR/SignalR/blob/master/SignalR/Infrastructure/DefaultDependencyResolver.cs. I'm still getting the same errors when I use your code, I think what I may need to be overriding though is Register with a call to the base constructor not the GetServices. I'm going to give that a try. – rball May 15 '12 at 16:12
  • @Steven: correct, i think also DefaultDependencyResolver is flawed. i tried almost every approach and i still can't inject dependencies. – Amr Ellafy Sep 20 '12 at 09:23
  • @AmrEllafy: My issue got closed by the developer as duplicate, but in general he agreed with my reasoning. Isn't there a fix or update yet that solves this? – Steven Sep 20 '12 at 09:34
  • 1
    @Steven: I ended up injecting my dependencies directly DependencyResolver.Current.GetService(); I couldn't find a cleaner way, at least with Spring.Net – Amr Ellafy Sep 20 '12 at 12:19
4

From SignalR 2.0 (and the beta's) there is a new way of setting the dependency resolver. SignalR moved to OWIN startup to do the configuration. With Simple Injector you'd do it like this:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HubConfiguration()
        {
            Resolver = new SignalRSimpleInjectorDependencyResolver(Container)
        };
        app.MapSignalR(config);
    }
}

public class SignalRSimpleInjectorDependencyResolver : DefaultDependencyResolver
{
    private readonly Container _container;
    public SignalRSimpleInjectorDependencyResolver(Container container)
    {
        _container = container;
    }
    public override object GetService(Type serviceType)
    {
        return ((IServiceProvider)_container).GetService(serviceType)
               ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType)
            .Concat(base.GetServices(serviceType));
    }
}

You'd have to explicitly inject your hubs like so:

container.Register<MessageHub>(() => new MessageHub(new EFUnitOfWork()));

This config is running live on a high traffic website without problems.

Mrchief
  • 75,126
  • 20
  • 142
  • 189
Elger Mensonides
  • 6,930
  • 6
  • 46
  • 69
  • A short version of the GetService() body would be: return ((IServiceProvider)container).GetService(serviceType) ?? base.GetService(serviceType); – blueling Oct 11 '13 at 12:15
  • A short version of GetServices() body would be: return container.GetAllInstances(serviceType).Concat(base.GetServices(serviceType)); – blueling Oct 11 '13 at 12:16
  • I updated the answer according to @blueling's feedback. btw, there was a bug in the `GetServices` method. The code checked for `GetRegistration(serviceType, false) != null`, but this predicate will usually yield false, since a registration for a collection is a different registration. Doing `GetRegistration(typeof(IEnumerable<>).MakeGenericType(serviceType), false) != null` however will not work, since this will never return null (since Simple Injector makes this registration for you if its missing). So blueling's answer is much better. – Steven May 02 '14 at 13:06
  • 7
    Where do you get an instance of `Container` from in your `Startup` class? – Makotosan May 08 '14 at 17:54
  • I stuck this in my Startup class, GlobalHost.DependencyResolver = new SignalRSimpleInjectorDependencyResolver(container); – Tony May 21 '14 at 00:08
  • Down-voted this answer, as it is not clear where to obtain the `Container` instance in the following piece of code: `var config = new HubConfiguration() { Resolver = new SignalRSimpleInjectorDependencyResolver(Container) };` – Jon Pawley Oct 12 '17 at 11:39
  • 1
    I'm confused: If you've still got to explicitly inject your dependencies on hub registration then what's the point of all of this? – Tom Feb 19 '18 at 16:57
0

The following worked for me. In addition, you will need to register a delegate with the container for your hub class before instantiating the dependency resolver.

ex: container.Register<MyHub>(() =>
        {
            IMyInterface dependency = container.GetInstance<IMyInterface>();

            return new MyHub(dependency);
        });

public class SignalRDependencyResolver : DefaultDependencyResolver
{
    private Container _container;
    private HashSet<Type> _types = new HashSet<Type>();

    public SignalRDependencyResolver(Container container)
    {
        _container = container;

        RegisterContainerTypes(_container);
    }

    private void RegisterContainerTypes(Container container)
    {
        InstanceProducer[] producers = container.GetCurrentRegistrations();

        foreach (InstanceProducer producer in producers)
        {
            if (producer.ServiceType.IsAbstract || producer.ServiceType.IsInterface)
                continue;

            if (!_types.Contains(producer.ServiceType))
            {
                _types.Add(producer.ServiceType);
            }
        }
    }

    public override object GetService(Type serviceType)
    {
        return _types.Contains(serviceType) ? _container.GetInstance(serviceType) : base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _types.Contains(serviceType) ? _container.GetAllInstances(serviceType) : base.GetServices(serviceType);
    }
}
Kirk
  • 16,182
  • 20
  • 80
  • 112
mg712
  • 1
0

In .NET Core 3.x the signalR is now a Transient. You can inject your hub using DI. So you start to map the hub, implement the interface and the hub, and access it's context via DI.

Startup:

app.UseSignalR(routes =>
{
    routes.MapHub<YourHub>(NotificationsRoute); // defined as string
});
services.AddSignalR(hubOptions =>
{
    // your options
})

Then you implement a interface like:

public interface IYourHub
{
    // Your interface implementation
}

And your hub:

public class YourHub : Hub<IYourHub>
{
    // your hub implementation
}

Finally you inject the hub like:

private IHubContext<YourHub, IYourHub> YourHub
{
    get
    {
        return this.serviceProvider.GetRequiredService<IHubContext<YourHub, IYourHub>>();
    }
}

Also you can define a service for your hub (middleware) and don't inject the context directly to your class.

Imagine you defined in the interface the method Message so in your class you can send the message like this:

await this.YourHub.Clients.Group("someGroup").Message("Some Message").ConfigureAwait(false);

If not implemented in a interface you just use:

await this.YourHub.Clients.Group("someGroup").SendAsync("Method Name", "Some Message").ConfigureAwait(false);
Kiril1512
  • 3,231
  • 3
  • 16
  • 41