我们有一个具体的单例服务,它实现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,所有的属性仍然存在)。
答案 0 :(得分:29)
Ninject 3.0现在支持绑定调用中的多个泛型类型,您可以在单个链式语句中轻松完成您要执行的操作。
kernel.Bind<IService1, IService2>()
.To<ConcreteService>()
.InSingletonScope();
您正在设置两个不同的绑定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);
....
}
}