SignalR 2 Dependency Injection with Ninject
Asked Answered
M

3

14

I have an existing MVC application that is using Dependency Injection with Ninject. I installed the Ninject.MVC3 nuget package and it creates a class called NinjectWebCommon in my App_Start, which completely isolates the kernel and registers all of my bindings:

public static void Start()
{
    DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
    DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
    bootstrapper.Initialize(CreateKernel);
}

private static IKernel CreateKernel()
{
    var kernel = new StandardKernel();
    kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
    kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
    RegisterServices(kernel);
    return kernel;
}

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IFoo>().To<Foo>();
}

We have a new requirement that we thought SignalR would be able to satisfy, so we installed SignalR 2 nuget package into the project. I created a Hub and did some searching on how to implement Dependency Injection into the project and found an article that suggests creating a SignalRDependencyResolver. http://www.asp.net/signalr/overview/signalr-20/extensibility/dependency-injection

The article has you creating a kernel in the Startup.cs file that is used for registering SignalR in OWIN:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {

        var kernel = new StandardKernel();
        var resolver = new NinjectSignalRDependencyResolver(kernel);

        kernel.Bind<IStockTicker>()
            .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()  // Bind to StockTicker.
            .InSingletonScope();  // Make it a singleton object.

        kernel.Bind<IHubConnectionContext>().ToMethod(context =>
            resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
            ).WhenInjectedInto<IStockTicker>();

        var config = new HubConfiguration()
        {
            Resolver = resolver
        };

        app.MapSignalR(config);

    }
}

The problem is that this approach has me creating two different kernels and they seem to have their own set of dependencies that they know how to resolve. If I have a dependency defined in NinjectWebCommon, the Hub doesn't know how to resolve that dependency. Without exposing my kernel in NinjectWebCommon, what is the proper way to add DI into SignalR using the Ninject.MVC3 package?

Merari answered 22/1, 2014 at 14:41 Comment(0)
F
31

None of the current answers directly answer your question. Also achieving the result you are after is very straightforward once you know exactly what to do. The "proper" way to do this is to set SignalR's dependency resolver in the CreateKernel method of the NinjectWebCommon class.

Assuming you have created a NinjectSignalRDependencyResolver class as you mention, no other code needs to be added anywhere except for the line highlighted in the code snippet below:

private static IKernel CreateKernel()
{
    var kernel = new StandardKernel();
    kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
    kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

    // THIS LINE DOES IT!!! Set our Ninject-based SignalRDependencyResolver as the SignalR resolver
    GlobalHost.DependencyResolver = new NinjectSignalRDependencyResolver(kernel);

    RegisterServices(kernel);
    return kernel;
}

Apart from the above, nothing more needs to be done except declaring your bindings in the RegisterServices method of NinjectWebCommon. In your example this would look like:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IStockTicker>()
        .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()  // Bind to StockTicker.
        .InSingletonScope();  // Make it a singleton object.

    kernel.Bind<IHubConnectionContext>().ToMethod(context =>
        resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
        ).WhenInjectedInto<IStockTicker>();
}

Except for the NinjectSignalRDependencyResolver class you created, no other code needs to be added. Importanly, the OwinStartup class remains unmodified, as follows:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR();
    }
}

The above example achieves the following important outcomes which were what you asked in your question:

  • You only have a single Ninject Kernel created
  • The kernel and all binding configurations remain confined to NinjectWebCommon
  • The default SignalR resolver is your NinjectSignalRDependencyResolver
  • Dependency Injection into all SignalR hubs is achieved

Hopefully this helps people out.

Find answered 30/3, 2014 at 21:31 Comment(6)
This. Please mark this as the answer. I can't find any better way to do this. I have created a gist with this in effect, thanks to this post. gist.github.com/darylteo/5a6b3656cedac556c83bHeavensent
Please can someone tell me where the resolver comes from in the RegisterServices() method?Narcosynthesis
The resolver reference was actually just copy pasted from the OP's code example. In the context of the RegisterServices() method, if resolver was declared as a local variable it would be equivalent to GlobalHost.DependencyResolverFind
Thanks, this was an immensely helpful simplification of the impenetrable explanation on MSDN. However, I think the Microsoft advice to bind kernel.Bind<IHubConnectionContext>() is also unnecessary---I don't understand why their example creates a dependency from the IStockTicker to the SignalR context. IMHO the Hub should be responsible for communicating with clients via SignalR and the stock ticker should know nothing about SignalR internals, so really all you need is: 1) GlobalHost.DependencyResolver line, 2) the kernel.Bind<IStockTicker>() line and of course 3) MapSignalR.Decameter
One thing I do not understand here is the reference to the type <Microsoft.AspNet.SignalR.StockTicker.StockTicker> this does not exist for me, and I do not understand how you are getting this?Illbehaved
The StockTicker class is a custom class that is specific to an MSDN article that the user was following as a guide. You can follow this example here: asp.net/signalr/overview/advanced/dependency-injection. In your project this type will not exist.Find
R
2

Have you tried adding the StockTickerHub itself to your kernel?

By default, SignalR uses Activator.CreateInstance to construct Hubs without any constructor arguments. If you want to inject your own dependencies into a Hub, you can do so by registering the Hub with SignalR's dependency resolver.

https://github.com/SignalR/SignalR/blob/2.0.1/src/Microsoft.AspNet.SignalR.Core/Hubs/DefaultHubActivator.cs#L28

If you want to get really creative, you can register your own IHubActivator instead of registering all of Hubs individually.

I go into more detail in how Hubs are created by default in this answer: SignalR with IoC (Castle Windsor) - which lifetime for hubs?

Randee answered 22/1, 2014 at 20:7 Comment(5)
I tried adding StockTickerHub to my kernel but it has dependencies that I don't know how to create...particularly the StockTicker class has a dependency on IHubConnectionContext. The article resolves that using its custom SignalRDependencyResolver, but as I said in my question I don't know how to integrate the two kernels.Merari
Ninject will try to construct unbound concrete types by default so it seems that adding the Hub manually is unnecessary: #12398645Randee
Still doesn't answer my question. I don't know how to put the two pieces together. The Stock Ticker sample has you create a kernel in the Startup.cs, but I'm also creating a kernel in NinjectWebCommon, and they don't know about each other.Merari
Why can't you share the kernel you create in NinjectWebCommon to create the NinjectSignalRDependencyResolver? I agree that using one kernel makes sense.Randee
Read the question again. The way NinjectWebCommon creates the kernel it is private to the class and not exposed in any way. It's not easy to share it without completely redoing that.Merari
P
1

There is a problem with the singleton scope. I don´t know who should get the blame here (Ninject, SignalR, MVC, etc...), but it works if you use ToConstant:

var binding = Bind<IMustBeSingleton>().ToConstant(new MustBeSingleton());

I had the same problem, and I found the solution: SignalR, WebAPI and MVC sharing the same dependency resolver kernel

I shared a complete solution with MVC, WebAPI and SignalR using the same Ninject kernel: https://drive.google.com/file/d/0B52OsuSSsroNX0I5aWFFb1VrRm8/edit?usp=sharing

That example web app, contains a single page that shows the AppDomain and GetHashCode of an object that is supposed to be unique across the three frameworks, giving a result similar to:

Dependency Test

Framework   IMySingletonService instance
MVC         AppDomainId:2 / HashCode:5109846
WebAPI      AppDomainId:2 / HashCode:5109846
SignalR     AppDomainId:2 / HashCode:5109846

I hope this helps.

Pepin answered 26/1, 2014 at 13:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.