Prevent Ninject from calling Initialize multiple times when binding to several interfaces

后端 未结 3 1448
太阳男子
太阳男子 2020-12-28 17:55

We have a concrete singleton service which implements Ninject.IInitializable and 2 interfaces. Problem is that services Initialize-methdod is called 2 times, wh

相关标签:
3条回答
  • 2020-12-28 18:06

    Update : Pretty sure using V3's multiple Bind overloads will address this; See this Q/A


    Good question.

    From looking at the source, the initialize bit happens after each Activate. Your Bind...ToMethod counts as one too. The strategy is pretty uniformly applied - there's no way to opt out in particular cases.

    Your workaround options are to use an explicit OnActivation in your Bind which will do it conditionally (but to do that in a general way would require maintaining a Set of initialized objects (havent looked to see if there is a mechanism to stash a flag against an activated object)), or to make your Initialize idempotent through whatever means is cleanest for you.

    EDIT:

        internal interface IService1
        {
        }
    
        internal interface IService2
        {
        }
    
        public class ConcreteService : IService1, IService2, Ninject.IInitializable
        {
            public int CallCount { get; private set; }
            public void Initialize()
            {
                ++CallCount;
            }
        }
    
        public class ServiceModule : NinjectModule
        {
            public override void Load()
            {
                this.Singleton<IService1, IService2, ConcreteService>();
            }
        }
    

    Given the following helpers:

    static class Helpers
    {
        public static void Singleton<K, T>( this NinjectModule module ) where T : K
        {
            module.Bind<K>().To<T>().InSingletonScope();
        }
    
        public static void Singleton<K, L, T>( this NinjectModule module )
            where T : K, L
        {
            Singleton<T, T>( module );
            module.Bind<K>().ToMethod( n => n.Kernel.Get<T>() );
            module.Bind<L>().ToMethod( n => n.Kernel.Get<T>() );
        }
    }
    

    @Ian Davis et al. The problem is that:

        class Problem
        {
            [Fact]
            static void x()
            {
                var kernel = new StandardKernel( new ServiceModule() );
                var v1 = kernel.Get<IService1>();
                var v2 = kernel.Get<IService2>();
                var service = kernel.Get<ConcreteService>();
                Console.WriteLine( service.CallCount ); // 3
                Assert.AreEqual( 1, service.CallCount ); // FAILS
            }
        }
    

    Because each activation (per Bind) initialises each time.

    EDIT 2: Same when you use the following slightly more stripped down version:

    static class Helpers
    {
        public static void Singleton<K, L, T>( this NinjectModule module )
            where T : K, L
        {
            module.Bind<T>().ToSelf().InSingletonScope();
            module.Bind<K>().ToMethod( n => n.Kernel.Get<T>() );
            module.Bind<L>().ToMethod( n => n.Kernel.Get<T>() );
        }
    }
    
    0 讨论(0)
  • 2020-12-28 18:19

    I think one of the option is, you create the object your self in the module and bind your object the each of the interfaces.

    BTW, try not to use any container specific code in your production code. If you have to do that, use some helper and isolate them in the module project.

    public class ServiceModule : NinjectModule
    {
    
        public override void Load()
        { 
             ConcreteService svc = new ConcreteService();
             Bind<IService1>().ToConstant(svc);
             Bind<IService2>().ToConstant(svc);
             ....
         }
    }
    
    0 讨论(0)
  • 2020-12-28 18:22

    Ninject 3

    Ninject 3.0 now supports multiple generic types in the call to bind, what you are trying to do can be easily accomplished in a single chained statement.

    kernel.Bind<IService1, IService2>()
          .To<ConcreteService>()
          .InSingletonScope();
    

    Ninject 2

    You are setting up two different bindings K=>T and L=>T. Requesting instances of L will return transient instances of T. Requesting K will return a singleton instance of T.

    In Ninject 2.0, an objects scope is per service interface bound to a scope callback.

    When you have

    Bind<IFoo>...InSingletonScope();
    Bind<IBar>...InSingletonScope();
    

    you are creating two different scopes.

    You are saying "Binding to IFoo will resolve to the same object that was returned when .Get was called." and "Binding to IBar will resolve to the same object that was returned when .Get was called."

    you can chain the bindings together, but you will need to remove IInitializable as it will cause duplicate initialization when the instance is activated:

    kernel.Bind<IBoo>()
          .To<Foo>()
          .InSingletonScope();
          .OnActivation(instance=>instance.Initialize());
    
    kernel.Bind<IBaz>()
          .ToMethod( ctx => (IBaz) ctx.Kernel.Get<IBoo>() );
    

    or

    kernel.Bind<Foo>().ToSelf().InSingletonScope()
        .OnActivation(instance=>instance.Initialize());
    kernel.Bind<IBaz>().ToMethod( ctx => ctx.Kernel.Get<Foo>() );
    kernel.Bind<IBoo>().ToMethod( ctx => ctx.Kernel.Get<Foo>() );
    

    in order to get multiple interfaces to resolve to the same singleton instance. When I see situations like this, I always have to ask, is your object doing too much if you have a singleton with two responsibilities?

    0 讨论(0)
提交回复
热议问题