Unity IoC InjectionFactory 不遵守 DependencyOverride

Unity IoC InjectionFactory not respecting DependencyOverride

我目前正在尝试在装饰器中包装一个 class 并在运行时注入其中一个依赖项。我目前有一个 IStorage 的接口,由 StorageCacheDecoratorStorage 实现。 StorageCacheDecorator 接受一个 IStorageStorage object takes in aContext` 对象。 然而,每次解析这些 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 构建的 policiesIBuilderContext 提供了一个 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 不会将其提供给您。可能只有扩展程序和朋友可以访问它。

有趣的事实:IBuilderContextGetResolverOverride 但没有任何种类的 .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..