我正在试用 Ninject,我正在修改我用 Structure Map 编写的代码,看看它有多容易。在这个基本代码中,我有一个对象图,这些对象通过结构图注册表具有不同的配置,并且在运行时通过数据库中的值选择要使用的对象(在这种情况下,拉回注入(inject)了一些对象的 wcf 服务主体) .例如(使用结构图代码):
注册表 1 为 IBusinessContext、IRules 和 ILogger 类型设置所有默认值。这只是在接口(interface)旁边添加 GenericContext/Logger/Rules 类型,没有其他专门化。
public GenericRegistry()
{
// Set up some generic bindings here
For<ILogger>().Use<Loggers.GenericLogger>();
For<IBusinessRule>().Use<Rules.StandardRule>();
For<IBusinessContext>().Use<Contexts.GenericBusinessContext>();
For<ILoggerContext>().Use<Loggers.GenericLoggerContext>();
}
Registry 2 设置 IBusinessContext 以使用 SpecialisedContext 类并告知构造函数使用 SpecializedLogger。 IBusinessContext 的实例名为“SpecializedContext”。
public SpecializedRegistry()
{
// Old style syntax as it affects the default for IBusinessContext
// Perhaps a hint at what I'm doing?
InstanceOf<IBusinessContext>().Is.OfConcreteType<Contexts.SpecializedBusinessContext>().Named(SpecializedInstanceName).Ctor<ILogger>().Is<Loggers.SpecialisedLogger>();
}
这一切都在结构图中按预期工作(取决于旧语法或新语法)。
但是,当我一直在使用 Ninject 时,我遇到了一个问题,即期望未命名的实例是默认的(不是 Ninject 的工作方式,我明白了)。这导致了一些研究,这些研究都表明使用命名实例是一个非常糟糕的主意。我知道有更好的方法可以使用自动注册或属性来设置名称或请求某种类型,但在我描述的系统中需要有一种方法在运行时找出要请求的配置在树的顶部(并让 IoC 框架根据注册的类型或规则找出其余部分)。
所以...我只是在这里错误地使用了 IoC 概念,期望按名称请求我的顶级对象,还是通常有更好的方法来做我想做的事情?我是否应该改用 MEF 之类的东西并将其视为插件?
我强调,我并没有像一个愚蠢的工厂那样使用它,并在代码的每个级别询问容器中类型 x 的实例,这只是启动操作。
提前感谢您的时间和帮助:)
最佳答案
按名称设置 ninject 绑定(bind)并没有什么错,如果这是实现你所需要的 IMO 的唯一方法。
所以基本语法是:
Bind<IBusinessContext>().To<ConcreteBusinessContext>().Named("XYZ");
或者如果您需要一个特定的调用类来获得不同的绑定(bind),那么您可以尝试:
Bind<IIBusinessContext>().To<SomeOtherConcreteBusinessContext>().WhenInjectedInto<TypeOfCallingClass>();
但是,如果调用类(我说的是在其 ctor 中具有 IBusinessContext 的类)提供了一个配置值来确定要加载的具体类型,那么您将需要使用委托(delegate):
Bind<Func<string, IBusinessContext>>().ToMethod(ctx => str => DetermineWhichConcreteTypeToLoad(ctx, str));
//messy sudo code
static DetermineWhichConcreteTypeToLoad(IContext ctx, string str)
{
if(str == "somevalue"){
return ctx.Kernel.Get<ConcreteType1>();
else
return ctx.Kernel.Get<ConcreteType2>();
}
你的调用类看起来像这样:
class DoStuff
{
Func<string, IBusinessContext>> contextFunc;
DoStuff(Func<string, IBusinessContext>> contextFunc)
{
this.contextFunc = contextFunc;
}
void SomeMethod()
{
var configuredValue = GetConfiguredValueSomehow();
var context = contextFunc(configuredValue); //<-- this passes your config value back to ninject in the ToMethod() lambda
//do something with context
}
}
在那个例子中不需要命名实例,因为你有一个加载特定具体类型的方法,但是如果你想做这样的事情,你仍然可以使用命名实例:
Bind<IBusinessContext>().To<ConcreteBusinessContext>().Named("config1");
Bind<IBusinessContext>().To<SomeOtherBusinessContext>().Named("config2");
Bind<Func<string, IBusinessContext>>().ToMethod(ctx => str => ctx.Kernel.Get<IBusinessContext>().Named(str));
class DoStuff
{
Func<string, IBusinessContext>> contextFunc;
DoStuff(Func<string, IBusinessContext>> contextFunc)
{
this.contextFunc = contextFunc;
}
void SomeMethod()
{
var configuredValue = "config1";
var context = contextFunc(configuredValue); //<-- this will passthrough "config1" to the above ToMethod() method and ask for a IBusinessContext named "config1"
}
}
编辑:我忘了提到如果您的配置值不必来自调用代码,那么这会让事情变得容易得多。您的代码可以类似于:
// this method can just be a global method in you app somewhere
static string GetConfigValue()
{
//something like
return AppSetting.Get("config");
}
Bind<IBusinessContext>().To<ConcreteBusinessContext>().When(r => GetConfigValue() == "config1");
Bind<IBusinessContext>().To<SomeOtherBusinessContext>().When(r => GetConfigValue() == "config2");
class DoStuff
{
IBusinessContext context;
DoStuff(BusinessContext context)
{
this.context = context;
}
void SomeMethod()
{
//use the context value as you normally would
}
}
您可以发挥创意而不是使用魔术字符串,您的配置方法可以加载一个枚举,您的 When() 方法可以测试枚举而不是字符串是否相等,但您明白了。这在 ninject 中被称为上下文绑定(bind),作为 SM 曾经的狂热用户,我可以告诉你,这比 SM 拥有的任何东西都强大得多。检查其余的 When() 方法,看看您可以做什么。
关于c# - 使用名称来区分使用 IoC 的实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9278982/