绑定到多个接口时,防止Ninject多次调用Initialize

时间:2010-06-15 08:03:49

标签: ninject

我们有一个具体的单例服务,它实现Ninject.IInitializable和2个接口。问题是服务Initialize-methdod被调用2次,只需要一次。我们使用的是.NET 3.5和Ninject 2.0.0.0。

Ninject中是否存在防止这种情况发生的模式。这两个接口都没有实现Ninject.IInitializable。服务类是:

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

模块看起来像这样:

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

其中Singleton是一个定义如下的扩展方法:

    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>());
    }

当然我们可以将bool initialized-member添加到ConcreteService并仅在它为false时进行初始化,但它看起来有点像黑客。并且它需要在实现两个或更多接口的每个服务中重复相同的逻辑。


感谢所有答案!我从所有人那里学到了一些东西! (我很难确定哪一个标记正确)。

我们最终创建了IActivable接口并扩展了ninject内核(它还删除了很好的代码级依赖关系到ninject,所有的属性仍然存在)。

3 个答案:

答案 0 :(得分:29)

Ninject 3

Ninject 3.0现在支持绑定调用中的多个泛型类型,您可以在单个链式语句中轻松完成您要执行的操作。

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

Ninject 2

您正在设置两个不同的绑定K =&gt; T和L =&gt; T.请求L的实例将返回T的瞬态实例。请求K将返回T的单例实例。

在Ninject 2.0中,对象范围是绑定到范围回调的每个服务接口。

当你有

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

您正在创建两个不同的范围。

你在说 “绑定到IFoo将解析为返回的同一对象 什么时候.Get被叫了。“ 和 “绑定到IBar将解析为返回的同一对象 什么时候.Get被叫了。“

您可以将绑定链接在一起,但是您需要删除 IInitializable ,因为它会在实例激活时导致重复初始化:

kernel.Bind<IBoo>()
      .To<Foo>()
      .InSingletonScope();
      .OnActivation(instance=>instance.Initialize());

kernel.Bind<IBaz>()
      .ToMethod( ctx => (IBaz) ctx.Kernel.Get<IBoo>() );

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>() );

为了让多个接口解析为同一个单例实例。当我看到这样的情况时,我总是要问,如果你有一个有两个职责的单身人士,你的对象是否做得太多了?

答案 1 :(得分:2)

更新:非常确定使用V3's multiple Bind overloads会解决此问题;见this Q/A


好问题。

从查看源代码,初始化位发生在每个Activate之后。您的Bind...ToMethod也算作一个。该策略非常统一 - 在特定情况下无法选择退出。

您的解决方法选项是在OnActivation中使用明确的Bind,这将有条件地执行(但是以一般方式执行此操作需要维护一组初始化对象(还没有看到如果有一种机制来对一个激活的对象存放一个标志)),或者通过任何最干净的方式使你的Initialize幂等。(/ p>

编辑:

    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>();
        }
    }

给出以下帮助者:

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等。问题是:

    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
        }
    }

因为每次激活(每Bind)都会初始化。

编辑2:当您使用以下稍微更精简的版本时,相同:

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>() );
    }
}

答案 2 :(得分:0)

我认为其中一个选择是,您在模块中创建自己的对象,并将对象绑定到每个接口。

顺便说一句,尽量不要在生产代码中使用任何特定于容器的代码。如果必须这样做,请使用一些帮助程序并将它们隔离在模块项目中。

public class ServiceModule : NinjectModule
{

    public override void Load()
    { 
         ConcreteService svc = new ConcreteService();
         Bind<IService1>().ToConstant(svc);
         Bind<IService2>().ToConstant(svc);
         ....
     }
}
相关问题