Unity IoC InjectionFactory 不遵守 DependencyOverride
Unity IoC InjectionFactory not respecting DependencyOverride
我目前正在尝试在装饰器中包装一个 class 并在运行时注入其中一个依赖项。我目前有一个 IStorage
的接口,由 StorageCacheDecorator
和 Storage
实现。 StorageCacheDecorator
接受一个 IStorage
和 Storage object takes in a
Context` 对象。 然而,每次解析这些 classes 时都需要传入上下文对象。
public interface IStorage
{
}
public class Storage : IStorage
{
public Context Context { get; }
public Storage(Context context)
{
this.Context = context;
}
}
public class StorageCacheDecorator : IStorage
{
public IStorage InnerStorage { get; }
public StorageCacheDecorator(IStorage innerStorage)
{
this.InnerStorage = innerStorage;
}
}
public class Context
{
}
我省略了实现细节,下面的测试给出了我的问题的示例
[Test]
public void ShouldResolveWithCorrectContext()
{
var context = new Context();
var container = new UnityContainer();
container.RegisterType<Storage>();
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
c.Resolve<Storage>())));
var resolve = container.Resolve<IStorage>(new DependencyOverride<Context>(context));
Assert.That(resolve, Is.TypeOf<StorageCacheDecorator>());
var cacheDecorator = ((StorageCacheDecorator)resolve);
Assert.That(cacheDecorator.InnerStorage, Is.TypeOf<Storage>());
var storage = ((Storage)cacheDecorator.InnerStorage);
Assert.That(storage.Context, Is.SameAs(context));
}
但是,如果我们删除装饰器,测试就会通过
[Test]
public void ShouldResolveWithCorrectContext1()
{
var context = new Context();
var container = new UnityContainer();
container.RegisterType<IStorage, Storage>();
var resolve = container.Resolve<IStorage>(new DependencyOverride<Context>(context));
Assert.That(resolve, Is.TypeOf<Storage>());
Assert.That(((Storage)resolve).Context, Is.SameAs(context));
}
如何让 InjectionFactory
尊重 DependencyOverride
?
首先,我很确定您(以及今天 - 我)偶然发现的是 Unity 中的错误。
多亏了 this excellent article,我从那里得到了一个 BuilderStrategy
的例子,我才设法诊断出它。在我用这个扩展替换我的 InjectionFactory 之后,它在某些情况下有效,但在其他情况下无效。
经过一些调查,似乎 Unity 中的所有分辨率都针对相同的 Container
及其配置工作,并且任何 DependencyOverrides
都被拖到 IBuilderContext
对象中的调用中,在一组 resolverOverrides
中,其中包含从 DependencyOverrides
构建的 policies
。 IBuilderContext
提供了一个 GetResolverOverride(Type)
方法,允许实现获取给定类型的值覆盖。
因此,如果您明确要求 IBuilderContext.GetResolverOverride
覆盖您的存储 Context
,您将获得您期望的 Same-Context-Object。
但是,如果您尝试询问 Container 本身,您将得到一个按照标准规则解析的 Context 对象。不是在解决问题时被覆盖。
这就是为什么在 InjectionFactory 委托中尝试 container.Resolve(..)
的原因如下:
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
c.Resolve<Storage>()))); // <-- this C is Container
将无法满足覆盖,因为..容器不知道覆盖!
它必须是:
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
builderContext.Resolve<Storage>())));
如果实施,可以访问 GetResolverOverride
并构建具有正确覆盖的适当存储。但是,不存在这样的方法,更糟糕的是,在代码的这一点上您无权访问 IBuilderContext
- InjectionFactory 不会将其提供给您。可能只有扩展程序和朋友可以访问它。
有趣的事实:IBuilderContext
有 GetResolverOverride
但没有任何种类的 .Resolve
。因此,如果您从那篇文章中获取代码,并且如果您使用那篇文章中的 PreBuildUp
来执行您自己的解析逻辑,则您必须使用 Container(并在解析器覆盖时失败),或者陷入检查一切并手动执行构建实例所需的所有子分辨率的地狱。 IBuilderContext
为您提供了一个漂亮的 NewBuildUp()
方法,这似乎是一个很好的捷径,但是..它使用基本容器并且不转发解析器覆盖。
看到它是多么复杂和不直观,以及不小心丢弃那组解析器覆盖并回退到普通上下文是多么容易,我很确定 InjectionFactory 是 EVIL/BUGGED/MISDESIGNED/etc:它给你 "c",您的主要 Container 实例,因此您无法根据覆盖正确解析参数。我们应该得到某种派生容器或额外的构建器对象等,而不是那个主容器。
使用文章中找到的代码,我能够破解一些使用此 GetResolverOverride
以我想要的方式初始化新对象实例的东西,但它绝不是通用的并且完全不可重用(我只是调用了适当的 .GetResolverOverride 来获取值并将其直接传递给 new MyObject(value)
.. 但是,上帝,这太糟糕了。但是,我需要作为测试设置的一部分,所以我可以接受它直到代码得到重构。
现在,让我们回到您的案例。显然你可以做类似的事情,但事实证明,在 decorators 的情况下,有一个更简单的方法:只是摆脱 InjectionFactory。您的原始实例初始化代码可能更复杂,但如果有任何机会它实际上与您的示例一样简单:
container.RegisterType<IStorage>(
new InjectionFactory(c =>
new StorageCacheDecorator( // <- NEW &
c.Resolve<Storage>() // <- RESOLVE
)));
你应该实际使用声明方式而不是命令式错误 InjectionFactory:
container.RegisterType<IStorage, StorageCacheDecorator>(
new InjectionConstructor(
new ResolvedParameter<Storage>()
));
最终效果完全相同:创建新对象,调用单参数构造函数,解析 Storage
并将其用作该参数。我用你的测试用例试过了,效果很好。
除了“ResolvedParameter”之外,您还可以使用直接对象实例,例如:
container.RegisterType<IStorage, StorageCacheDecorator>(
new InjectionConstructor(
"foo", 5, new Shoe()
));
但是,就像您原来的示例中的 new StorageDecorator
一样,InjectionConstructor 需要获取构造函数的 all 参数,显然我们不会获取将来构造函数参数更改时的编译时错误。它也比 InjectionFactory 更受限制,因为它需要预先指定所有参数。
更有理由讨厌 Unity..
我目前正在尝试在装饰器中包装一个 class 并在运行时注入其中一个依赖项。我目前有一个 IStorage
的接口,由 StorageCacheDecorator
和 Storage
实现。 StorageCacheDecorator
接受一个 IStorage
和 Storage object takes in a
Context` 对象。 然而,每次解析这些 classes 时都需要传入上下文对象。
public interface IStorage
{
}
public class Storage : IStorage
{
public Context Context { get; }
public Storage(Context context)
{
this.Context = context;
}
}
public class StorageCacheDecorator : IStorage
{
public IStorage InnerStorage { get; }
public StorageCacheDecorator(IStorage innerStorage)
{
this.InnerStorage = innerStorage;
}
}
public class Context
{
}
我省略了实现细节,下面的测试给出了我的问题的示例
[Test]
public void ShouldResolveWithCorrectContext()
{
var context = new Context();
var container = new UnityContainer();
container.RegisterType<Storage>();
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
c.Resolve<Storage>())));
var resolve = container.Resolve<IStorage>(new DependencyOverride<Context>(context));
Assert.That(resolve, Is.TypeOf<StorageCacheDecorator>());
var cacheDecorator = ((StorageCacheDecorator)resolve);
Assert.That(cacheDecorator.InnerStorage, Is.TypeOf<Storage>());
var storage = ((Storage)cacheDecorator.InnerStorage);
Assert.That(storage.Context, Is.SameAs(context));
}
但是,如果我们删除装饰器,测试就会通过
[Test]
public void ShouldResolveWithCorrectContext1()
{
var context = new Context();
var container = new UnityContainer();
container.RegisterType<IStorage, Storage>();
var resolve = container.Resolve<IStorage>(new DependencyOverride<Context>(context));
Assert.That(resolve, Is.TypeOf<Storage>());
Assert.That(((Storage)resolve).Context, Is.SameAs(context));
}
如何让 InjectionFactory
尊重 DependencyOverride
?
首先,我很确定您(以及今天 - 我)偶然发现的是 Unity 中的错误。
多亏了 this excellent article,我从那里得到了一个 BuilderStrategy
的例子,我才设法诊断出它。在我用这个扩展替换我的 InjectionFactory 之后,它在某些情况下有效,但在其他情况下无效。
经过一些调查,似乎 Unity 中的所有分辨率都针对相同的 Container
及其配置工作,并且任何 DependencyOverrides
都被拖到 IBuilderContext
对象中的调用中,在一组 resolverOverrides
中,其中包含从 DependencyOverrides
构建的 policies
。 IBuilderContext
提供了一个 GetResolverOverride(Type)
方法,允许实现获取给定类型的值覆盖。
因此,如果您明确要求 IBuilderContext.GetResolverOverride
覆盖您的存储 Context
,您将获得您期望的 Same-Context-Object。
但是,如果您尝试询问 Container 本身,您将得到一个按照标准规则解析的 Context 对象。不是在解决问题时被覆盖。
这就是为什么在 InjectionFactory 委托中尝试 container.Resolve(..)
的原因如下:
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
c.Resolve<Storage>()))); // <-- this C is Container
将无法满足覆盖,因为..容器不知道覆盖!
它必须是:
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
builderContext.Resolve<Storage>())));
如果实施,可以访问 GetResolverOverride
并构建具有正确覆盖的适当存储。但是,不存在这样的方法,更糟糕的是,在代码的这一点上您无权访问 IBuilderContext
- InjectionFactory 不会将其提供给您。可能只有扩展程序和朋友可以访问它。
有趣的事实:IBuilderContext
有 GetResolverOverride
但没有任何种类的 .Resolve
。因此,如果您从那篇文章中获取代码,并且如果您使用那篇文章中的 PreBuildUp
来执行您自己的解析逻辑,则您必须使用 Container(并在解析器覆盖时失败),或者陷入检查一切并手动执行构建实例所需的所有子分辨率的地狱。 IBuilderContext
为您提供了一个漂亮的 NewBuildUp()
方法,这似乎是一个很好的捷径,但是..它使用基本容器并且不转发解析器覆盖。
看到它是多么复杂和不直观,以及不小心丢弃那组解析器覆盖并回退到普通上下文是多么容易,我很确定 InjectionFactory 是 EVIL/BUGGED/MISDESIGNED/etc:它给你 "c",您的主要 Container 实例,因此您无法根据覆盖正确解析参数。我们应该得到某种派生容器或额外的构建器对象等,而不是那个主容器。
使用文章中找到的代码,我能够破解一些使用此 GetResolverOverride
以我想要的方式初始化新对象实例的东西,但它绝不是通用的并且完全不可重用(我只是调用了适当的 .GetResolverOverride 来获取值并将其直接传递给 new MyObject(value)
.. 但是,上帝,这太糟糕了。但是,我需要作为测试设置的一部分,所以我可以接受它直到代码得到重构。
现在,让我们回到您的案例。显然你可以做类似的事情,但事实证明,在 decorators 的情况下,有一个更简单的方法:只是摆脱 InjectionFactory。您的原始实例初始化代码可能更复杂,但如果有任何机会它实际上与您的示例一样简单:
container.RegisterType<IStorage>(
new InjectionFactory(c =>
new StorageCacheDecorator( // <- NEW &
c.Resolve<Storage>() // <- RESOLVE
)));
你应该实际使用声明方式而不是命令式错误 InjectionFactory:
container.RegisterType<IStorage, StorageCacheDecorator>(
new InjectionConstructor(
new ResolvedParameter<Storage>()
));
最终效果完全相同:创建新对象,调用单参数构造函数,解析 Storage
并将其用作该参数。我用你的测试用例试过了,效果很好。
除了“ResolvedParameter”之外,您还可以使用直接对象实例,例如:
container.RegisterType<IStorage, StorageCacheDecorator>(
new InjectionConstructor(
"foo", 5, new Shoe()
));
但是,就像您原来的示例中的 new StorageDecorator
一样,InjectionConstructor 需要获取构造函数的 all 参数,显然我们不会获取将来构造函数参数更改时的编译时错误。它也比 InjectionFactory 更受限制,因为它需要预先指定所有参数。
更有理由讨厌 Unity..