Can I use Ninject to instantiate singleton services that nothing depends on?

99封情书 提交于 2019-12-01 07:32:15

You cannot have ninject instantiate stuff in case you don't ask it to instantiate something yourself. The simple way is to ask ninject to instantiate things at composition root:

var kernel = new StandardKernel();
kernel.Bind<IFoo>().To<Foo>();
kernel.Load(AppDomain.CurrentDomain.GetAssemblies()); // loads all modules in assemlby
//...
// resolution root completely configured

kernel.Resolve<IFooSingleton>();
kernel.Resolve<IBarSIngleton>();

There is one alternative, actually, which is not the same, but can be used to achieve a similar effect. It requires that there is at least one single other service instantiated soon enough: Ninject.Extensions.DependencyCreation. It works like this:

kernel.Bind<string>().ToConstant("hello");

kernel.Bind<ISingletonDependency>().To<SingletonDependency>()
    .InSingletonScope();
kernel.DefineDependency<string, ISingletonDependency>();

kernel.Get<string>();
// when asking for a string for the first time
// ISingletonDependency will be instantiated.
// of course you can use any other type instead of string

Why

Ninject is unlike some other containers (for example Autofac) not "built" in stages. There's no concept of first creating the bindings, and then creating the kernel to use them. The following is perfectly legal:

kernel.Bind<IFoo>()...
kernel.Get<IFoo>()...
kernel.Bind<IBar>()...
kernel.Get<IBar>()...

so ninject can't possibly know when you want the singletons to be instantiated. With autofac it's clear and easy:

var containerBuilder = new ContainerBuilder();

containerBuilder
    .RegisterType<Foo>()
    .AutoActivate();

var container = containerBuilder.Build(); // now

Coming from Guice in Java, I've sorely missed the pattern of eager singletons. They are useful in scenarios where for example modules act as plugins. If you imagine that a service is assembled from modules that are specified in a configuration, you could see a problem of then also trying to specify what this module needs to be auto-instantiated when the application is started.

For me the module is where the composition of the application is defined and separating eager singletons into another place in the code feels more clunky and less intuitive.

Anyway, I've been able to very easily implement this as a layer on top of Ninject, here's the code:

public static class EagerSingleton
{
    public static IBindingNamedWithOrOnSyntax<T> AsEagerSingleton<T>(this IBindingInSyntax<T> binding)
    {
        var r = binding.InSingletonScope();

        binding.Kernel.Bind<IEagerSingleton>().To<EagerSingleton<T>>().InSingletonScope();

        return r;
    }
}

public interface IEagerSingleton { }

public class EagerSingleton<TComponent> : IEagerSingleton
{
    public EagerSingleton(TComponent component)
    {
        // do nothing. DI created the component for this constructor.
    }
}

public class EagerSingletonSvc
{
    public EagerSingletonSvc(IEagerSingleton[] singletons)
    {
        // do nothing. DI created all the singletons for this constructor.
    }
}

After you've created your kernel, add a single line:

kernel.Get<EagerSingletonSvc>(); // activate all eager singletons

You use it in a module like this:

Bind<UnhandledExceptionHandlerSvc>().ToSelf().AsEagerSingleton();
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!