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

3

20

We have a concrete singleton service which implements Ninject.IInitializable and 2 interfaces. Problem is that services Initialize-methdod is called 2 times, when only one is desired. We are using .NET 3.5 and Ninject 2.0.0.0.

Is there a pattern in Ninject prevent this from happening. Neither of the interfaces implement Ninject.IInitializable. the service class is:

public class ConcreteService : IService1, IService2, Ninject.IInitializable
{
    public void Initialize()
    {
        // This is called twice!
    }
}

And module looks like this:

public class ServiceModule : NinjectModule
{
    public override void Load()
    {
        this.Singleton<Iservice1, Iservice2, ConcreteService>();
    }
}

where Singleton is an extension method defined like this:

    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<K, T>(module);
        module.Bind<L>().ToMethod(n => n.Kernel.Get<T>());
    }

Of course we could add bool initialized-member to ConcreteService and initialize only when it is false, but it seems quite a bit of a hack. And it would require repeating the same logic in every service that implements two or more interfaces.


Thanks for all the answers! I learned something from all of them! (I am having a hard time to decide which one mark correct).

We ended up creating IActivable interface and extending ninject kernel (it also removed nicely code level dependencies to ninject, allthough attributes still remain).

Kayak answered 15/6, 2010 at 8:3 Comment(5)
Doesnt IStartable get called multiple times same as Initializable? (Maybe it doesnt...)Countrydance
It did, at least the way described in this question.Kayak
BTW v3 has a cleaner interface for binding multiple interfaces on an type (Though I'm not sure if it addresses the multiple-IInitialization issue. If anyone needs the answer, I suggest asking a new question and no doubt @Remo Gloor will pounce on it!Countrydance
See #10206549Countrydance
see also #3148496Countrydance
E
29

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?

Elka answered 15/6, 2010 at 12:20 Comment(5)
You sure? Added an edit to my answer - AIUI each activation of a Bind causes an Initialization (Very surprised if I'm not missing something though!)Countrydance
Updated. I forgot about the IInitializable part.Elka
Now agree with the advice and agree it addresses the OP's question better. Not sure I agree with the way you paint two Bind...InSingletonScope as creating different scope contexts - the scoping is ultimately to the same root.Countrydance
Dont have a PC now but need to double check that each Kernel.Get<Foo>() invocation in ToMethod isnt a separate 'activation' - i.e., I think it still might get called multiple times (if it did, the fluent syntax is in a confusing order). I really wish someone would sort out the dojo update on this -- I can reverse engineer small issues but dont get the zen (pun intended) of this area.Countrydance
Note there is a proper solution (other that Then Dont Do That) in V3 now:- planetgeek.ch/2011/12/30/…Countrydance
C
2

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>() );
    }
}
Countrydance answered 15/6, 2010 at 8:54 Comment(0)
M
0

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);
         ....
     }
}
Meredeth answered 15/6, 2010 at 12:3 Comment(3)
Wont work - the problem is that Ninject's IInitialize interface is called for every activation (i.e., creation as part of a Bind stream). Your example boils down to the same thing as the question and hence will end up initializing twice too.Countrydance
I would say in this way, implementing IInitialize is totally unnecessary, and is a bad practice - if some day you have to switch to another container, your code needs to be changed, you have to remember it was initialized by container, and if you forget that and just removed the interface to make it compile, you will get runtime problem. On the other hand, if a "new" can handle it, why make it so complicated, anyway, container is to serve us to make code simpler, not harder, and the Moudle's purpose is to construct object, I don't mind "new" it myself.Meredeth
I agree with your general sentiments (constructor injection, no container-specifics leaking into business logic etc.). My key point and the reason for the -1 is that a) your code is the same as the code in the question that the OP said didnt work b) it thus doesnt answer or solve the question. The purpose of IInitializable is to facilitate two phase construction and can be useful if you have to rsort to prop injection (e.g. if your environment rules out constructor injection). But that's all a digression that has little to do with the question.Countrydance

© 2022 - 2024 — McMap. All rights reserved.