2017-02-20 6 views
1

私は現在、クラスをデコレータでラップし、実行時に依存関係の1つに注入しようとしています。私は現在、StorageCacheDecoratorStorageで実装されているIStorageのインターフェイスを持っています。 StorageCacheDecoratorIStorageStorage object takes in a Context`オブジェクトを取ります。 しかし、これらのクラスが解決されるたびにコンテキストオブジェクトを渡す必要があります。Unity IoC InjectionFactory DependencyOverrideを尊重しない

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 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)); 
    } 

を渡すデコレータを削除する場合

[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)); 
    } 

しかし私の問題の例を示しますInjectionFactoryDependencyOverrideを尊重しますか?

+0

私は同じ問題で戦っています。実際には、複雑なセットアップも必要ありません。あなたがデコレータを持っていなくても、ファクトリデリゲートを持つStorageを登録していれば、問題は同じです。あなたは偶然に何か解決策を見つけましたか? – quetzalcoatl

+1

@quetzalcoatl私はこのIoCコンテナをあきらめて解決策を見つけませんでした。 –

+0

Heh。ええ、私は同じことができ、DryIocで何かをすることができればいいと思います。でも、ウィンザーはもっと予測可能です。しかし、この古いプロジェクトではコードを変更するにはあまりにも多くのコードを、それにはあまりにも時間がかかりません。迅速な返信をありがとう!たぶん私は何かを見つけて後でそれを掲示するでしょう。 – quetzalcoatl

答えて

1

まず第一に、あなた(そして今日 - 私)が遭遇したのはUnityのバグです。

this excellent articleに私はBuilderStrategyという例があります。私のInjectionFactoryをこの拡張で置き換えた後、いくつかのケースでは機能し、他のものでは機能しませんでした。いくつかの調査の後

は、ユニティ内のすべての解像度は DependencyOverridesから構築 policies含ま resolverOverridesのセットで、 IBuilderContextオブジェクト内のコールに沿ってドラッグされているのと同じ Containerとその構成、および任意の DependencyOverridesに対して動作しているようです。 IBuilderContextには GetResolverOverride(Type)メソッドが用意されています。このメソッドを使用すると、指定されたタイプの値オーバーライドを取得できます。

したがって、IBuilderContext.GetResolverOverrideにストレージContextのオーバーライドを明示的に尋ねると、期待通りの同じコンテキストオブジェクトが得られます。

しかし、コンテナ自体に依頼しようとすると、標準規則に従って解決されたContextオブジェクトが取得されます。それは解像度でオーバーライドされたものではありません。

ここのようなInjectionFactoryデリゲートでcontainer.Resolve(..)しようとする理由は次のとおりです。

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

が..コンテナがオーバーライドについての考えを持っていないので、上書きを満たすために失敗します!

それはでなければならない:、これまで実施された場合、GetResolverOverrideにアクセスして、正しいオーバーライドとの適切なストレージを構築することができ

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

。しかし、そのようなメソッドは存在しません。さらに悪いことに、コードのこの時点では、IBuilderContextへのアクセス権がありません。InjectionFactoryはそれをあなたに与えません。たぶん拡張子&だけがそれにアクセスできます。

楽しい事実:IBuilderContextGetResolverOverrideですが、.Resolveはありません。だから、その記事のコードを読んで、その記事のようにPreBuildUpを使って独自の解決ロジックを行ったのであれば、コンテナを使用する(そしてリゾルバの上書きに失敗する)か、地獄に入るすべてを検査し、インスタンスを構築するために必要なすべてのサブ解像度を手動で行います。 IBuilderContextはきれいに見えるNewBuildUp()メソッドを提供します。これはすばらしいショートカットですが、ベースコンテナを使用し、リゾルバのオーバーライドを転送しません。

それがどれほど複雑で直観的であるかを見て、誤ってそのような解決策のオーバーライドとフォールバックをバニラのコンテキストに落とすのはいかがですか、私はInjectionFactoryがEVIL/BUGGED/MISDESIGNED/etcであると確信しています。あなたのメインのコンテナインスタンスである "c"であるため、オーバーライドに関してパラメータを適切に解決する方法がありません。その主なコンテナの代わりに、何らかの派生コンテナまたは余分なビルダーオブジェクトなどを取得する必要があります。

記事にあるコードを使用して、私はこのオブジェクトを初期化するためにこのGetResolverOverrideをハックすることができました私が望んでいた方法でインスタンス化しましたが、それは一般的ではなく、全く再利用できませんでした(値を取得するために適切な.GetResolverOverrideを呼び出し、それをnew MyObject(value)に正しく渡しました..しかし、神様、それはひどいです。テストセットアップの一部ですので、コードがリファクタリングされるまでそれを使用することができます。

これであなたのケースに戻ってきます。明らかに同様のことができますが、デコレータの場合 th ereははるかに簡単な方法です:ただ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`」は:ちょうどあなたの元の例ではnew StorageDecoratorと同様に、InjectionConstructorがためのパラメータのすべてを取得する必要があり、

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

が、将来的にコンストラクタのパラメータが変更された場合、コンパイル時エラーは発生しません。 InjectionFactoryよりもはるかに制限されています。すべてのパラメータを前もって指定する必要があるからです。

Unityを憎むための理由がもっとあります。

関連する問題