是否有可能和/或一个好主意,用Ninject(或任何其他IoC容器,对于这个问题)来创建一个默认绑定适用于不存在适当实现的情况,并使用此默认绑定而不是在多个绑定存在时处理ActivationException
,或者对于特定请求不存在绑定?使用Ninject和有约束力的默认实现,同时避免可怕的服务定位器反模式
我一直在使用Ninject的Factory和Conventions扩建项目去过,但我想知道,如果他们掩盖一个错误,我在更根本的层面进行,所以我创建了一个测试来说明我想要什么这样做,因为简单,因为我可以:
考虑以下几点:
public interface IWidget { }
public class DefaultWidget : IWidget { }
public class BlueWidget : IWidget { }
而且使用FluentAssertions以下xUnit测试:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// intention: resolve a `DefaultWidget` implementation whenever the
// 'name' parameter does not match the name of any other bound implementation
kernel.Bind<IWidget>().To<DefaultWidget>();
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
}
我不确定这条路究竟如何走下去,如果甚至可能的话,会导致我对服务定位器模式的执行不当,除非它似乎是那些回答类似问题的人给出的警告。
所以,这是IoC容器的滥用/误用做我要求做的事情吗?
看来我应该想要将类型与IoC绑定/解析为所有显式类型,因此我认为这部分看起来并不错。在现实生活中,会有很多更明确的绑定像BlueWidget
,以及对“RedWidget”字符串值在未来未知数量的变化。
一般来说,这似乎是一个默认实现的概念的一些接口是一种并不罕见的情况,那么如果不在IoC容器的领域内,这种解析请求的机制在哪里?
我还计划使用工厂模式创建IWidget
实现。到目前为止,我一直使用Ninject.Extensions.Factory自动创建的工厂与自定义实例提供程序和自定义绑定生成器,但我无法解决这个问题。
对工厂实现有更多的控制(换句话说,使用我自己的工厂,而不是Ninject.Extensions.Factory的自动工厂)有帮助吗?过去,我使用Assembly反射来查找候选类型,并使用Activation.CreateInstance()来创建我需要的特定实现或默认实现,但是一旦这些实现具有它们自己的构造函数注入的依赖关系,这会变得非常麻烦因为依赖注入原则适用于这些实现。因此,我转向IoC容器寻求解决方案 - 但这并不像我希望的那样工作。
更新1 - 成功用自己的工厂实现
我不是这个真的很开心,因为每一个新的执行IWidget
必须写的时候,我不得不敲开这家工厂并更新它。在我的例子中,我不得不在绑定中添加另一行 - 但这就是基于约定的绑定会出现的地方,我打算使用这种绑定来避免不断更新绑定定义。
使用此工厂实现,
public interface IWidgetFactory { IWidget Create(string name); }
public class WidgetFactory : IWidgetFactory
{
private readonly IKernel kernel;
public WidgetFactory(IKernel kernel) { this.kernel = kernel; }
public IWidget Create(string name)
{
switch (name)
{
case "Blue":
return this.kernel.Get<IWidget>(typeof (BlueWidget).Name);
default:
return this.kernel.Get<IWidget>(typeof (DefaultWidget).Name);
}
}
}
我能得到这个测试通过:
[Fact]
public void WidgetBuilders_And_Customizers_And_Bindings_Oh_My()
{
StandardKernel kernel = new StandardKernel();
kernel.Bind<IWidget>().To<DefaultWidget>().Named(typeof(DefaultWidget).Name);
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
kernel.Bind<IWidgetFactory>().To<WidgetFactory>().InSingletonScope();
kernel.Get<IWidgetFactory>().Create("Blue")
.Should().BeOfType<BlueWidget>();
kernel.Get<IWidgetFactory>().Create("Red")
.Should().BeOfType<DefaultWidget>();
}
它的工作原理,但感觉不对,有以下原因:
- 我必须将
IKernel
注入IWidgetFactory
- 对于每个新执行的
IWidget
的IWidgetFactory
必须更新 - 这似乎是一个Ninject扩展名应该为这种情况已经
UPDATE OF END 1
会是怎样被创造你在这种情况下做的是,考虑到IWidget
实现的计数很高,“widget name”参数的预期范围本质上是无限的,并且所有不可解析的widget名称应该被处理为w ith DefaultWidget
?
你不需要进一步的阅读,但如果你有兴趣,这里是我试过,因为我曾在这个问题上的各种测试:
下面是测试的完整进化我经历了:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// evolution #1: simple as possible
// PASSES (as you would expect)
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #2: make the only binding to a different IWidget
// FAILS (as you would expect)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #3: add the binding to `BlueWidget` back
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #4: make `BlueWidget` binding a *named binding*
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #5: change `Get<>` request to specifiy widget name
// PASSES (yee-haw!)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #6: make `BlueWidget` binding *non-named*
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #7: ask for non-existance `RedWidget`, hope for `DefaultWidget`
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #8: make `BlueWidget` binding a *named binding* again
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #9: remove `RedWidget` specification in Get<> request
// ACTIVATION EXCEPTION (back to **more than one** binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<DefaultWidget>();
}
你问一个很好的问题,并已明确提出在大量的工作和我的很高兴+1。我个人很困惑#8的结果并手动验证!您的下一步是查看命名绑定的测试,并且a)尝试理解它们以改进您的问题b)尝试理解它,以便您可以使用自己的绑定元数据实现您的解决方案(相信我这很容易)但理想情况下,您会从测试中发现您观察的所有内容是否有意义,然后提交错误报告或合并请求或对您的问题进行更改 –
@Ruben,感谢您的评论 - 这些测试是在主要Ninject GitHub存储库?我找不到像“NamedBindingsTests”这样的测试文件,尽管我在“Planning”文件夹中找到了BindingMetadata。 – Jeff
邓诺 - 只是猜测基于我以前的探索,2s ...似乎https://github.com/ninject/ninject/blob/master/src/Ninject.Test/Integration/StandardKernelTests.cs是最接近的。无法回想起是否存在维基,博客文章或被广告投放的关于默认命名行为的答案,因为您问的是,但我非常肯定它在某处(我个人做的都是全名或全名,所以它从来没有作为关注) –