Unity IoC InjectionFactory不尊重DependencyOverride

时间:2017-02-20 15:13:09

标签: c# inversion-of-control unity-container

我目前正在尝试在装饰器中包装一个类,并在运行时注入其中一个依赖项。我目前有IStorage的界面,由StorageCacheDecoratorStorage实施。 StorageCacheDecorator接收IStorageStorage object takes in a Context`对象。 但是,每次解析这些类时都需要传递上下文对象。

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

1 个答案:

答案 0 :(得分:3)

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

我设法通过this excellent article从我得到一个BuilderStrategy的例子中得到了一点诊断。在我用这个扩展替换了我的InjectionFactory之后,它适用于某些情况,并且不适用于其他情况。

经过一些调查后,似乎Unity中的所有分辨率都针对相同的Container及其配置,并且任何DependencyOverrides都会在IBuilderContext对象的调用中拖动resolverOverrides集,其中包含policies构建的DependencyOverridesIBuilderContext提供了GetResolverOverride(Type)方法,该方法允许实现获取给定Type的值覆盖。

因此,如果您明确要求IBuilderContext.GetResolverOverride覆盖您的存储Context,您将获得您期望的相同上下文对象。

但是,如果您尝试询问Container本身,您将获得一个根据标准规则解析的Context对象。不是那种在分辨率上被覆盖的。

这就是为什么在InjectionFactory委托中container.Resolve(..)的任何尝试都像这样:

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

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

必须是:

    container.RegisterType<IStorage>(
        new InjectionFactory(c => new StorageCacheDecorator(
            builderContext.Resolve<Storage>())));

如果实现了,可以访问GetResolverOverride并使用正确的覆盖构建正确的存储。但是,不存在这样的方法,更糟糕的是,在此代码中您无权访问IBuilderContext - InjectionFactory并不能提供给您。可能只有扩展程序和朋友可以访问它。

有趣的事实:IBuilderContextGetResolverOverride但没有任何种类的.Resolve。因此,如果您从该文章中获取代码,并且如果您使用该文章中的PreBuildUp来执行您自己的解析逻辑,那么您必须使用Container(并且在解析器覆盖时失败),或者深入研究一切,并手动完成构建实例所需的所有子分辨率。 IBuilderContext为您提供了一个漂亮的NewBuildUp()方法,这似乎是一个很好的捷径,但它使用基础容器而不转发解析器覆盖。

看到它是多么复杂和不直观,以及不小心丢弃那组解析器和回退到vanilla上下文是多么容易,我很确定InjectionFactory是EVIL / BUGGED / MISDESIGNED / etc:它给你“c “,您的主要Container实例,因此您无法根据覆盖正确解析参数。我们应该获得某种派生容器或额外的构建器对象等,而不是那个主容器。

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

现在,让我们回到你的案子。显然你可以做类似的事情,但事实证明,在装饰器的情况下,有一个更简单的方法:只需摆脱InjectionFactory。您的原始实例初始化代码可能更复杂,但如果它实际上只是在您的示例中那么简单:

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

你应该实际使用声明式方式而不是命令式错误注射工具:

    container.RegisterType<IStorage, StorageCacheDecorator>(
          new InjectionConstructor(
               new ResolvedParameter<Storage>()
          ));

净效果完全相同:创建新对象,调用单参数构造函数,并解析Storage并将其用作该参数。我尝试了你的测试用例,它运行得很好。

您也可以使用直接对象实例,而不是'ResolvedParameter`,例如:

    container.RegisterType<IStorage, StorageCacheDecorator>(
          new InjectionConstructor(
               "foo", 5, new Shoe()
          ));

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

仇恨团结的更多理由..