2009-01-23 42 views
8

我試圖清理我的代碼中的一些可訪問性的東西,並無意中破壞了Unity依賴注入。過了一段時間,我意識到我標記了一些公共屬性,我並不是真的想在我的DLL之外暴露給內部。然後我開始接受例外。Unity框架DependencyAttribute只適用於公共屬性?

因此,在Unity中使用[Dependency]屬性似乎只適用於公共屬性。我認爲這是有道理的,因爲內部和私人道具不會被Unity組件看到,但感覺真的很髒有一堆公共屬性,你從來沒有想要任何人設置或能夠設置,而不是Unity 。

有沒有辦法讓統一設置內部或私人屬性呢?

這是我希望看到通過的單元測試。目前只有公共道具測試通過:

[TestFixture] 
public class UnityFixture 
{ 
    [Test] 
    public void UnityCanSetPublicDependency() 
    { 
     UnityContainer container = new UnityContainer(); 
     container.RegisterType<HasPublicDep, HasPublicDep>(); 
     container.RegisterType<TheDep, TheDep>(); 

     var i = container.Resolve<HasPublicDep>(); 
     Assert.IsNotNull(i); 
     Assert.IsNotNull(i.dep); 
    } 

    [Test] 
    public void UnityCanSetInternalDependency() 
    { 
     UnityContainer container = new UnityContainer(); 
     container.RegisterType<HasInternalDep, HasInternalDep>(); 
     container.RegisterType<TheDep, TheDep>(); 

     var i = container.Resolve<HasInternalDep>(); 
     Assert.IsNotNull(i); 
     Assert.IsNotNull(i.dep); 
    } 

    [Test] 
    public void UnityCanSetPrivateDependency() 
    { 
     UnityContainer container = new UnityContainer(); 
     container.RegisterType<HasPrivateDep, HasPrivateDep>(); 
     container.RegisterType<TheDep, TheDep>(); 

     var i = container.Resolve<HasPrivateDep>(); 
     Assert.IsNotNull(i); 
     Assert.IsNotNull(i.depExposed); 
    } 
} 

public class HasPublicDep 
{ 
    [Dependency] 
    public TheDep dep { get; set; } 
} 

public class HasInternalDep 
{ 
    [Dependency] 
    internal TheDep dep { get; set; } 
} 

public class HasPrivateDep 
{ 
    [Dependency] 
    private TheDep dep { get; set; } 

    public TheDep depExposed 
    { 
     get { return this.dep; } 
    } 
} 

public class TheDep 
{ 
} 

更新時間:

我注意到調用堆棧設置從轉移財產:

UnityCanSetPublicDependency() 
--> Microsoft.Practices.Unity.dll 
--> Microsoft.Practices.ObjectBuilder2.dll 
--> HasPublicDep.TheDep.set() 

因此,企圖至少使內部版本工作,我將這些添加到我的程序集的屬性:

[assembly: InternalsVisibleTo("Microsoft.Practices.Unity")] 
[assembly: InternalsVisibleTo("Microsoft.Practices.Unity.Configuration")] 
[assembly: InternalsVisibleTo("Microsoft.Practices.ObjectBuilder2")] 

但是,沒有變化。 Unity/ObjectBuilder仍然不會設置內部屬性

+0

通過創建自定義擴展找到解決方案。看到我的答案在下面... – CodingWithSpike 2009-01-23 18:51:53

回答

2

好,經過在反射器周圍戳一堆,我想通了。 默認情況下,找到一個構造函數構造器注入調用的代碼:

ConstructorInfo[] constructors = typeToConstruct.GetConstructors() 

由於沒有的BindingFlags,這將只檢測公共構造。 對於一些掛羊頭賣狗肉(比如從反射器複製/粘貼),你可以做一個UnityContainerExtension做所有相同的東西,默認實現,但改變調用GetConstructors()來:

ConstructorInfo[] constructors = typeToConstruct..GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 

然後添加擴展進入統一容器。實現的擴展是〜100行代碼,所以我沒有在這裏粘貼。如果有人想要它,讓我知道...

新的工作測試案例。請注意,所有的統一創建類現在內部:

[TestFixture] 
public class UnityFixture 
{ 
    [Test] 
    public void UnityCanSetInternalDependency() 
    { 
     UnityContainer container = new UnityContainer(); 
     container.AddNewExtension<InternalConstructorInjectionExtension>(); 
     container.RegisterType<HasInternalDep, HasInternalDep>(); 
     container.RegisterType<TheDep, TheDep>(); 

     var i = container.Resolve<HasInternalDep>(); 
     Assert.IsNotNull(i); 
     Assert.IsNotNull(i.dep); 
    } 
} 


internal class HasInternalDep 
{ 
    internal HasInternalDep(TheDep dep) 
    { 
     this.dep = dep; 
    } 

    internal TheDep dep { get; set; } 
} 

internal class TheDep 
{ 
} 

我敢肯定,我可以提出延期做同樣的解決非公有制性質,但這些代碼是一個複雜多了:)

5

如果該屬性爲只讀,則使用contructor injection而不是屬性注入更有意義。

如果Unity 做了使用反射來設置私有或內部成員,它將受到代碼訪問安全約束。具體來說,它不適用於低信任環境。

+0

是真的,這可能是一個更乾淨的解決方案。仍然感到團結/ ob不能使用內部。哦,好的......謝謝! – CodingWithSpike 2009-01-23 17:05:21

0

基於Kent B的回答,我改爲使用構造函數注入,它可以用於公共類。然而,根本問題仍然存在,你想分配或由Unity分配的任何東西都必須公開。這包括類本身。

新的單元測試:

[TestFixture] 
public class UnityFixture 
{ 
    [Test] 
    public void UnityCanSetInternalDependency() 
    { 
     UnityContainer container = new UnityContainer(); 
     container.RegisterType<HasInternalDep, HasInternalDep>(); 
     container.RegisterType<TheDep, TheDep>(); 

     var i = container.Resolve<HasInternalDep>(); 
     Assert.IsNotNull(i); 
     Assert.IsNotNull(i.dep); 
    } 
    } 

internal class HasInternalDep 
{ 
    internal HasInternalDep(TheDep dep) 
    { 
     this._Dep = dep; 
    } 

    private TheDep _Dep; 
     internal TheDep dep 
     { 
      get { return _Dep; } 
     } 
} 

internal class TheDep 
{ 
} 
} 

與大會屬性:

[assembly: InternalsVisibleTo("Microsoft.Practices.Unity")] 
[assembly: InternalsVisibleTo("Microsoft.Practices.Unity.Configuration")] 
[assembly: InternalsVisibleTo("Microsoft.Practices.ObjectBuilder2")] 

與錯誤失敗:

The type HasInternalDep does not have an accessible constructor. 
at Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, String name) 

所以整體看來,如果你想使用統一,你基本上只需要把所有東西都公諸於衆。真的很醜的公用事業/圖書館.dll ...

5

另一種解決方案是在將依賴項傳遞給類的方法上使用[InjectionMethod]。

public class MyClass { 
private ILogger logger; 

[InjectionMethod] 
public void Init([Dependency] ILogger logger) 
{ 
    this.logger = logger; 

...等


,並調用它:

container.BuildUp<MyClass>(instanceOfMyClass); 

它將調用初始化與團結的依賴。

didn't相當解決的問題,我知道......但

:-)Ĵ

+0

很酷,我不知道這個屬性。謝謝! – CodingWithSpike 2009-02-17 19:56:45

0

這是我的內部構造噴油器擴展類:

大潛在的問題: 99這是來自.NET 4.1.0.0版本的來自.NET反射器的Unity代碼的複製/粘貼。較新版本的Unity可能會改變實現並打破這種擴展,或導致flakey錯誤。你被警告!

using System; 
using System.Collections.Generic; 
using System.Globalization; 
using System.Reflection; 
using Microsoft.Practices.ObjectBuilder2; 
using Microsoft.Practices.Unity; 
using Microsoft.Practices.Unity.ObjectBuilder; 
using Microsoft.Practices.Unity.Utility; 

namespace MyApp.Unity.Configuration 
{ 
    /// <summary> 
    /// This extension changes the behavior of Unity constructor injection to allow the use of non-public constructors. 
    /// By default, Unity/ObjectBuilder would call Type.GetConstructors() to get the constructors. With the default binding 
    /// flags, this only returns public constructors. 
    /// The code here is 99% copy/paste from Reflector's dissassembly of the default Unity/OB implementation. 
    /// My only change was to add binding flags to get all constructors, not just public ones. 
    /// For more info, see: Microsoft.Practices.Unity.ObjectBuilder.DefaultUnityConstructorSelectorPolicy 
    /// </summary> 
    public class InternalConstructorSelectorPolicy : IConstructorSelectorPolicy 
    { 
     protected IDependencyResolverPolicy CreateResolver(ParameterInfo param) 
     { 
      List<DependencyResolutionAttribute> list = new List<DependencyResolutionAttribute>(Sequence.OfType<DependencyResolutionAttribute>(param.GetCustomAttributes(false))); 
      if (list.Count > 0) 
      { 
       return list[0].CreateResolver(param.ParameterType); 
      } 
      return new NamedTypeDependencyResolverPolicy(param.ParameterType, null); 
     } 

     private SelectedConstructor CreateSelectedConstructor(IBuilderContext context, ConstructorInfo ctor) 
     { 
      SelectedConstructor constructor = new SelectedConstructor(ctor); 
      foreach (ParameterInfo info in ctor.GetParameters()) 
      { 
       string buildKey = Guid.NewGuid().ToString(); 
       IDependencyResolverPolicy policy = this.CreateResolver(info); 
       context.PersistentPolicies.Set<IDependencyResolverPolicy>(policy, buildKey); 
       DependencyResolverTrackerPolicy.TrackKey(context.PersistentPolicies, context.BuildKey, buildKey); 
       constructor.AddParameterKey(buildKey); 
      } 
      return constructor; 
     } 

     private ConstructorInfo FindInjectionConstructor(Type typeToConstruct) 
     { 
      ConstructorInfo[] infoArray = Array.FindAll<ConstructorInfo>(typeToConstruct.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), delegate(ConstructorInfo ctor) 
      { 
       return ctor.IsDefined(typeof(InjectionConstructorAttribute), true); 
      }); 
      switch (infoArray.Length) 
      { 
       case 0: 
        return null; 

       case 1: 
        return infoArray[0]; 
      } 
      throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Resources.MultipleInjectionConstructors", new object[] { typeToConstruct.Name })); 
     } 

     private ConstructorInfo FindLongestConstructor(Type typeToConstruct) 
     { 
      ConstructorInfo[] constructors = typeToConstruct.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
      Array.Sort<ConstructorInfo>(constructors, new ConstructorLengthComparer()); 
      switch (constructors.Length) 
      { 
       case 0: 
        return null; 

       case 1: 
        return constructors[0]; 
      } 
      int length = constructors[0].GetParameters().Length; 
      if (constructors[1].GetParameters().Length == length) 
      { 
       throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Resources.AmbiguousInjectionConstructor", new object[] { typeToConstruct.Name, length })); 
      } 
      return constructors[0]; 
     } 

     public virtual SelectedConstructor SelectConstructor(IBuilderContext context) 
     { 
      Type typeToConstruct = BuildKey.GetType(context.BuildKey); 
      ConstructorInfo ctor = this.FindInjectionConstructor(typeToConstruct) ?? this.FindLongestConstructor(typeToConstruct); 
      if (ctor != null) 
      { 
       return this.CreateSelectedConstructor(context, ctor); 
      } 
      return null; 
     } 

     // Nested Types 
     private class ConstructorLengthComparer : IComparer<ConstructorInfo> 
     { 
      // Methods 
      public int Compare(ConstructorInfo x, ConstructorInfo y) 
      { 
       return (y.GetParameters().Length - x.GetParameters().Length); 
      } 
     } 
    } 

    /// <summary> 
    /// Registeres the InternalConstructorSelectorPolicy with the Unity container. 
    /// </summary> 
    public class InternalConstructorInjectionExtension : UnityContainerExtension 
    { 
     protected override void Initialize() 
     { 
      this.Context.Policies.SetDefault(typeof(IConstructorSelectorPolicy), new InternalConstructorSelectorPolicy()); 
     } 
    } 
} 
3

更新企業庫5.0

由於rally52rs警告可能發生,升級到EntLib5.0打破他的執行。使用與Rally相同的方法,我反映了新的代碼庫,並處理了以下5.0兼容版本的InternalConstructorSelectorPolicy。

請注意,我的版本明確地將自身限制在FindLongestConstructor方法的內部構造函數中。在這一點上,我的代碼在功能上與Rally的不同。

public class InternalConstructorSelectorPolicy : IConstructorSelectorPolicy, IBuilderPolicy 
{ 
    private IDependencyResolverPolicy CreateResolver(ParameterInfo parameter) 
    { 
     List<DependencyResolutionAttribute> attrs = parameter.GetCustomAttributes(false).OfType<DependencyResolutionAttribute>().ToList<DependencyResolutionAttribute>(); 
     if (attrs.Count > 0) 
     { 
      return attrs[0].CreateResolver(parameter.ParameterType); 
     } 
     return new NamedTypeDependencyResolverPolicy(parameter.ParameterType, null); 
    } 

    private SelectedConstructor CreateSelectedConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination, ConstructorInfo ctor) 
    { 
     SelectedConstructor result = new SelectedConstructor(ctor); 
     foreach (ParameterInfo param in ctor.GetParameters()) 
     { 
      string key = Guid.NewGuid().ToString(); 
      IDependencyResolverPolicy policy = this.CreateResolver(param); 
      resolverPolicyDestination.Set<IDependencyResolverPolicy>(policy, key); 
      DependencyResolverTrackerPolicy.TrackKey(resolverPolicyDestination, context.BuildKey, key); 
      result.AddParameterKey(key); 
     } 
     return result; 
    } 

    private static ConstructorInfo FindInjectionConstructor(Type typeToConstruct) 
    { 
     ConstructorInfo[] injectionConstructors = typeToConstruct 
      .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 
      .Where<ConstructorInfo>(delegate(ConstructorInfo ctor) 
     { 
      return ctor.IsDefined(typeof(InjectionConstructorAttribute), true); 
     }).ToArray<ConstructorInfo>(); 
     switch (injectionConstructors.Length) 
     { 
      case 0: 
       return null; 

      case 1: 
       return injectionConstructors[0]; 
     } 
     throw new InvalidOperationException(string.Format("Multiple constructors found for {0}" , typeToConstruct.Name)); 
    } 

    private static ConstructorInfo FindLongestConstructor(Type typeToConstruct) 
    { 
     var constructors = 
      Array.FindAll(
       typeToConstruct.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), 
       ctor => !ctor.IsFamily && !ctor.IsPrivate); //Filter out protected and private constructors 

     Array.Sort<ConstructorInfo>(constructors, new ConstructorLengthComparer()); 
     switch (constructors.Length) 
     { 
      case 0: 
       return null; 

      case 1: 
       return constructors[0]; 
     } 
     int paramLength = constructors[0].GetParameters().Length; 
     if (constructors[1].GetParameters().Length == paramLength) 
     { 
      throw new InvalidOperationException(string.Format("Ambiguous constructor found for {0}", typeToConstruct.Name)); 
     } 
     return constructors[0]; 
    } 

    public SelectedConstructor SelectConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination) 
    { 
     Type typeToConstruct = context.BuildKey.Type; 
     ConstructorInfo ctor = FindInjectionConstructor(typeToConstruct) ?? FindLongestConstructor(typeToConstruct); 
     if (ctor != null) 
     { 
      return this.CreateSelectedConstructor(context, resolverPolicyDestination, ctor); 
     } 
     return null; 
    } 

    // Nested Types 
    private class ConstructorLengthComparer : IComparer<ConstructorInfo> 
    { 
     // Methods 
     public int Compare(ConstructorInfo x, ConstructorInfo y) 
     { 
      return (y.GetParameters().Length - x.GetParameters().Length); 
     } 
    } 
} 
+0

我還沒有搬到EntLib5,所以這會爲我節省頭痛,每當我做...感謝代碼! – CodingWithSpike 2010-06-11 13:22:31

1

@ rally25rs,雖然職位是兩年多歲,它仍然穩居高位(視圖/谷歌等),所以我想我會加入我的2美分..我有同樣的問題,最終選擇了這個解決方案:UnityContainer and internal constructor。 這意味着作爲一個評論,但我還不能發表評論。

您可能已經看到並瞭解了這一點,但它可能對任何其他人都有用:InternalsVisibleTo()屬性應該永遠不會有效 - 這是因爲Unity不會直接調用您的類。相反,它使用反射並檢查Type。當然,Type不會因該屬性的存在而改變。爲了在接收方「享受」內部可見等的好處,您必須明確地呼叫內部c'tor(或財產)。

2

這個問題本身似乎是一個誤解。

關於核心聲明:

a bunch of public properties that you never want anyone to set or be able to set, other than Unity.

你會想將它們設置在單元測試中,或者是怎麼回事,你會通過依賴嘲弄? 即使你沒有單元測試,也有一個奇怪的想法,那就是擁有任何東西(除了Unity的一些魔法)都可以設置的依賴關係。你想讓你的代碼非常依賴支持工具嗎?

此外,具有公共屬性根本不是問題,因爲您的代碼必須依賴於接口,而不是實現(SOLID原則之一)。如果你不遵循這個原則 - 你沒有理由使用Unity。當然,你不會在界面中聲明依賴關係,所以消費類不知道它們。

你已經被告知使用構造函數注入更好,但屬性注入也有其美感。它允許以較少的修改添加新的依賴項(特別是,您可以避免更改現有的單元測試,只添加新的依賴項)。