Unity IoC InjectionFactory 不尊重 DependencyOverride

Kev*_*ith 2 c# inversion-of-control unity-container

我目前正在尝试在装饰器中包装一个类,并在运行时注入其中一个依赖项。我目前有一个由 a和 aIStorage实现的接口。接受a和Context` 对象。然而,每次解析这些类时都需要传入上下文对象。StorageCacheDecoratorStorageStorageCacheDecoratorIStorageStorage object takes in a

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
{
}
Run Code Online (Sandbox Code Playgroud)

我省略了实现细节,下面的测试给出了我的问题的示例

    [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));
    }
Run Code Online (Sandbox Code Playgroud)

但是,如果我们删除装饰器,测试就会通过

    [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));
    }
Run Code Online (Sandbox Code Playgroud)

我怎样才能得到InjectionFactory尊重DependencyOverride

que*_*atl 5

首先,我非常确定您(以及今天 - 我)偶然发现的是 Unity 中的一个错误。

多亏了这篇优秀的文章,我成功地诊断了它,我从那里得到了一个BuilderStrategy. 当我用这个扩展替换我的 InjectionFactory 后,它在某些情况下有效,但在其他情况下不起作用。

经过一番调查,Unity 中的所有分辨率似乎都针对相同的Container及其配置工作,并且任何分辨率都DependencyOverrides被拖动到一个IBuilderContext对象中的调用中,其中一组resolverOverrides包含policiesDependencyOverrides. 它IBuilderContext提供了一种GetResolverOverride(Type)方法,允许实现获取给定类型的值覆盖。

因此,如果您明确要求IBuilderContext.GetResolverOverride覆盖您的 Storage Context,您将获得您期望的 Same-Context-Object 。

但是,如果您尝试询问容器本身,您将得到一个按照标准规则解析的 Context 对象。并不是在解决问题时就被重写。

这就是为什么container.Resolve(..)在 InjectionFactory 委托中进行任何尝试的原因如下:

    container.RegisterType<IStorage>(
        new InjectionFactory(c => new StorageCacheDecorator(
            c.Resolve<Storage>())));  // <-- this C is Container
Run Code Online (Sandbox Code Playgroud)

将无法满足覆盖,因为..容器不知道覆盖!

它必须是:

    container.RegisterType<IStorage>(
        new InjectionFactory(c => new StorageCacheDecorator(
            builderContext.Resolve<Storage>())));
Run Code Online (Sandbox Code Playgroud)

如果实现的话,可以访问GetResolverOverride并构建具有正确覆盖的适当存储。然而,不存在这样的方法,更糟糕的是,在代码的这一点上,您无权访问IBuilderContext- InjectionFactory 没有将其提供给您。可能只有扩展和朋友才能访问它。

有趣的事实:IBuilderContextGetResolverOverride但没有任何类型的.Resolve. 因此,如果您从该文章中获取代码,并且使用PreBuildUp该文章中的 as 来执行自己的解析逻辑,则必须使用 Container(并在解析器覆盖上失败),或者陷入困境检查所有内容并手动执行构建实例所需的所有子分辨率。IBuilderContext为您提供了一个漂亮的NewBuildUp()方法,这似乎是一个很好的快捷方式,但是..它使用基本容器并且不转发解析器覆盖。

看到它是多么复杂和不直观,以及意外地删除那组解析器覆盖并回退到普通上下文是多么容易,我很确定 InjectionFactory 是邪恶的/有缺陷的/错误设计的/等等:它给你“c”,你的main Container 实例,因此您无法正确解析覆盖的参数。我们应该获得某种派生容器,或者额外的构建器对象等,而不是那个主容器。

使用文章中找到的代码,我能够以GetResolverOverride我想要的方式破解一些使用它来初始化新对象实例的东西,但它绝不是通用的并且完全不可重用(我只是调用了正确的 .GetResolverOverride 来获取value 并将其直接传递给new MyObject(value).. 但是,上帝,这太糟糕了。但是,我需要作为测试设置的一部分,所以我可以忍受它,直到代码被重构。

现在,让我们回到你的案例。显然你可以做类似的事情,但事实证明,对于装饰器来说,有一种更简单的方法:只需摆脱 InjectionFactory 即可。您的原始实例初始化代码可能更复杂,但如果有任何机会它实际上就像您的示例中一样简单:

    container.RegisterType<IStorage>(
        new InjectionFactory(c =>
            new StorageCacheDecorator(   // <- NEW &
                 c.Resolve<Storage>()    // <- RESOLVE
        )));
Run Code Online (Sandbox Code Playgroud)

您实际上应该使用声明式方式而不是命令式错误的 InjectionFactory:

    container.RegisterType<IStorage, StorageCacheDecorator>(
          new InjectionConstructor(
               new ResolvedParameter<Storage>()
          ));
Run Code Online (Sandbox Code Playgroud)

最终效果完全相同:创建新对象,调用单参数构造函数,并Storage解析并用作该参数。我用你的测试用例尝试过,效果很好。

除了“ResolvedParameter”之外,您还可以使用直接对象实例,例如:

    container.RegisterType<IStorage, StorageCacheDecorator>(
          new InjectionConstructor(
               "foo", 5, new Shoe()
          ));
Run Code Online (Sandbox Code Playgroud)

但是,就像new StorageDecorator在原始示例中一样,InjectionConstructor 需要获取构造函数的所有参数,显然,当构造函数参数将来发生更改时,我们不会收到编译时错误。它也比 InjectionFactory 受到更多限制,因为它需要预先指定所有参数。

更有理由讨厌 Unity..