SignalR, WebAPI and MVC sharing the same dependenc

2019-05-09 22:55发布

问题:

I have an ASP.NET MVC app with SignalR and WebAPI. The app uses Ninject for dependency injection, but apparently SignalR and WebAPI are getting different kernels, so it fails to share a singleton object that should be shared for all the application.

I can see clearly in the log how an instance is created when SignalR gets a connection request, and other when WebAPI gets a request.

I want to have the same Ninject kernel shared among these three elements, so I can have unique singletons.

This is what I have done so far:

The first thing I have done is creating a NinjectModule declaring the binding:

public class MyDependencyModule: NinjectModule
{
    public override void Load()
    {
        var binding = Bind<MustBeSingleton>().ToSelf();
        binding.OnActivation((ctx, o) =>
            {
                Debug.Print("Registering item " + o.GetHashCode());
                HostingEnvironment.RegisterObject(o);
            });

        binding.OnDeactivation(o =>
            {
                Debug.Print("Unregistering game connection " + o.GetHashCode());
            });

        binding.InSingletonScope();
    }
}

I have also created a wrapper for Ninject in order to plug it in WebAPI:

public class NinjectDependencyScope : IDependencyScope
{
    private IResolutionRoot resolver;

    internal NinjectDependencyScope(IResolutionRoot resolver)
    {
        this.resolver = resolver;
    }

    public void Dispose()
    {
        IDisposable disposable = resolver as IDisposable;
        if (disposable != null)
            disposable.Dispose();
        resolver = null;
    }

    public object GetService(Type serviceType)
    {
        if (resolver == null)
            throw new ObjectDisposedException("this", "This scope has already been disposed");

        return resolver.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        if (resolver == null)
            throw new ObjectDisposedException("this", "This scope has already been disposed");

        return resolver.GetAll(serviceType);
    }
}

public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
    private IKernel kernel;
    public NinjectDependencyResolver(IKernel kernel)
        : base(kernel)
    {
        this.kernel = kernel;
    }

    public IDependencyScope BeginScope()
    {
        return new NinjectDependencyScope(kernel.BeginBlock());
    }
}

Also, I have created another wrapper for SignalR:

public class SignalRNinjectDependencyResolver : DefaultDependencyResolver
{
    private readonly IKernel _kernel;

    public SignalRNinjectDependencyResolver(IKernel kernel)
    {
        _kernel = kernel;
    }

    public override object GetService(Type serviceType)
    {
        return _kernel.TryGet(serviceType) ?? base.GetService(serviceType);
    }

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

Then I have created a Ninject kernel that does all the config:

public class ApplicationDependencies:StandardKernel
{
    public ApplicationDependencies()
      :base(new MyDependencyModule())
    {
        System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(this);
        Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver = new SignalRNinjectDependencyResolver(this);
    }
}

The MVC application, uses NinjectHttpApplication as base class, so I indicate the kernel that must be used this way:

public class MvcApplication : Ninject.Web.Common.NinjectHttpApplication
{
    protected override Ninject.IKernel CreateKernel()
    {
        return new ApplicationDependencies();
    }
}

Also, in the SignalR configuration I specify the Resolver:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR<MyPersistentConnection>("/updates", new ConnectionConfiguration()
        {
            Resolver = GlobalHost.DependencyResolver
        });

    }
}

(I have tried also without specifying the resolver, and it does not work either).

Any idea?

Cheers.

回答1:

In order keep this 3 things working.. you should check these references out:

  1. Web API + Ninject http://www.peterprovost.org/blog/2012/06/19/adding-ninject-to-web-api/
  2. SignalR + Ninject https://github.com/SignalR/SignalR/wiki/Extensibility (last part: When using ASP.NET MVC, configure SignalR first, then ASP.NET MVC)

For the second one, I refactored a little bit, since I need the kernel for SignalR Dependency Resolver

// Route SignalR.
GlobalHost.DependencyResolver = NinjectWebCommon.GetSignalrResolver();
RouteTable.Routes.MapHubs();

I defined GetSignalrResolver inside of NinjectWebCommon like this:

public static Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver GetSignalrResolver()
{
    return new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(bootstrapper.Kernel);
}

Note: There are 2 different DependencyResolver: one for Web API (1) assigned to GlobalConfiguration.Configuration.DependencyResolver and the other for SignalR (2) assigned to GlobalHost.DependencyResolver



回答2:

I found the answer in another post: Singleton Scope binding not working as intended

Rather than binding as a singleton, "ToConstant" must be used:

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

I have created a simple demo project with ASP.NET MVC, WebAPI and SignalR using the same dependency injection context.

https://drive.google.com/file/d/0B52OsuSSsroNX0I5aWFFb1VrRm8/edit?usp=sharing

The 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

Other problem was, that Ninject was disposing my singleton because was IDisposable. I don't really understand why this happens, but that is another war.

Cheers.



回答3:

in order to use a dependency resolver for both WebApi and SignalR you need to implement a class that looks like this:

    public class NinjectDependencyResolver : Microsoft.AspNet.SignalR.DefaultDependencyResolver,
    System.Web.Http.Dependencies.IDependencyResolver
{
    public readonly IKernel Kernel;

    public NinjectDependencyResolver(string moduleFilePattern)
        : base()
    {
        Kernel = new StandardKernel();
        Kernel.Load(moduleFilePattern);

    }
    public override object GetService(Type serviceType)
    {
        var service = Kernel.TryGet(serviceType) ?? base.GetService(serviceType);
        return service;
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        IEnumerable<object> services = Kernel.GetAll(serviceType).ToList();
        if (services.IsEmpty())
        {
            services = base.GetServices(serviceType) ?? services;
        }
        return services;
    }

    public System.Web.Http.Dependencies.IDependencyScope BeginScope()
    {
        return this;
    }

    public void Dispose()
    { }
}

then in your startup class you should register NinjectDependencyResolver for both WebApi and SignalR, like this:

public void Configuration(IAppBuilder app)
{
    var dependencyResolver = new NinjectDependencyResolver("*.dll");

    var httpConfiguration = new HttpConfiguration();
    httpConfiguration.DependencyResolver = dependencyResolver;
    app.UseWebApi(httpConfiguration);

    var hubConfig = new HubConfiguration { Resolver = dependencyResolver };
    app.MapSignalR(hubConfig);
}