4

我想應用一些使用本地生長類型的「aspect」的行爲,真的是.net屬性。我有一個基礎類(BankingServiceBase),它在啓動時反映自己,看看應用了哪些「方面」。然後它可以在操作之前或之後執行自定義行爲。我使用Autofac作爲我的IOC容器。我正在嘗試將PropertiesAutowired方法應用於方面的註冊。在下面的示例代碼中,我希望Autofac爲我的aspect/attribute注入一個ILog實例。然而,它並沒有這樣做。我的猜測是,當我撥打GetCustomAttributes時,它會創建一個新實例,而不是從Autofac獲取已註冊的實例。思考?下面是一些可用的示例代碼顯示的問題:如何在.net屬性中注入屬性依賴關係?

internal class Program 
{ 
    private static void Main() 
    { 
     var builder = new ContainerBuilder(); 

     builder 
      .RegisterType<ConsoleLog>() 
      .As<ILog>(); 

     builder 
      .RegisterType<BankingService>() 
      .As<IBankingService>(); 

     builder 
      .RegisterType<LogTransfer>() 
      .As<LogTransfer>() 
      .PropertiesAutowired(); 

     var container = builder.Build(); 

     var bankingService = container.Resolve<IBankingService>(); 

     bankingService.Transfer("ACT 1", "ACT 2", 180); 

     System.Console.ReadKey(); 
    } 

    public interface IBankingService 
    { 
     void Transfer(string from, string to, decimal amount); 
    } 

    public interface ILog 
    { 
     void LogMessage(string message); 
    } 

    public class ConsoleLog : ILog 
    { 
     public void LogMessage(string message) 
     { 
      System.Console.WriteLine(message); 
     } 
    } 

    [AttributeUsage(AttributeTargets.Class)] 
    public abstract class BankingServiceAspect : Attribute 
    { 
     public virtual void PreTransfer(string from, string to, decimal amount) 
     { 
     } 

     public virtual void PostTransfer(bool success) 
     { 
     } 
    } 

    public class LogTransfer : BankingServiceAspect 
    { 
     // Note: this is never getting set from Autofac! 
     public ILog Log { get; set; } 

     public override void PreTransfer(string from, string to, decimal amount) 
     { 
      Log.LogMessage(string.Format("About to transfer from {0}, to {1}, for amount {2}", from, to, amount)); 
     } 

     public override void PostTransfer(bool success) 
     { 
      Log.LogMessage(success ? "Transfer completed!" : "Transfer failed!"); 
     } 
    } 

    public abstract class BankingServiceBase : IBankingService 
    { 
     private readonly List<BankingServiceAspect> aspects; 

     protected BankingServiceBase() 
     { 
      // Note: My guess is that this "GetCustomAttributes" is happening before the IOC dependency map is built. 
      aspects = 
       GetType().GetCustomAttributes(typeof (BankingServiceAspect), true).Cast<BankingServiceAspect>(). 
        ToList(); 
     } 

     void IBankingService.Transfer(string from, string to, decimal amount) 
     { 
      aspects.ForEach(a => a.PreTransfer(from, to, amount)); 

      try 
      { 
       Transfer(from, to, amount); 
       aspects.ForEach(a => a.PostTransfer(true)); 
      } 
      catch (Exception) 
      { 
       aspects.ForEach(a => a.PostTransfer(false)); 
      } 
     } 

     public abstract void Transfer(string from, string to, decimal amount); 
    } 

    [LogTransfer] 
    public class BankingService : BankingServiceBase 
    { 
     public override void Transfer(string from, string to, decimal amount) 
     { 
      // Simulate some latency.. 
      Thread.Sleep(1000); 
     } 
    } 
} 
+0

-1爲什麼在世界上,你會想要寫一個類反思自己?有幾個容器通過截取框來支持AOP。就[快速谷歌搜索顯示](http://code.google.com/p/autofac/wiki/DynamicProxy2)Autofac也支持這一點。 – 2012-02-22 10:03:42

回答

0

嘗試實施的方面

private readonly List<BankingServiceAspect> _aspects; 
private List<BankingServiceAspect> Aspects 
{ 
    get 
    { 
     if (_aspects == null) { 
      _aspects = GetType() 
       .GetCustomAttributes(typeof(BankingServiceAspect), true) 
       .Cast<BankingServiceAspect>() 
       .ToList(); 
     } 
     return _aspects; 
    } 
} 

懶加載然後使用它像這樣

Aspects.ForEach(a => a.PreTransfer(from, to, amount)); 
... 
+0

我試過Oliver,但它有相同的問題(Log依賴項爲空)。基於我的假設和Rich的評論,這是因爲Autofac無法將屬性注入到屬性中,因爲它們基本上是硬編碼到MSIL代碼中的。 – 2012-02-21 23:25:10

4

你是正確的那GetCustomAttributes不能通過Autofac解析自定義屬性 - 如果你仔細想一想,GetLibrary如何獲得自定義屬性的FCL代碼知道Autofac?自定義屬性實際上是從程序集元數據中檢索的,所以它們永遠不會通過Autofac的解析過程,因此您的註冊碼從不使用。

你可以做的是服務注入到屬性實例自己。從Oliver's answer中的代碼開始,生成方面屬性列表。但是,在返回列表之前,您可以處理每個屬性並將服務注入到任何相關字段和屬性中。我有一個名爲AttributedDependencyInjector的課程,我通過擴展方法使用。它使用反射來掃描用InjectDependencyAttribute裝飾的字段和屬性,然後設置這些屬性的值。有相當多的代碼來應付各種情況,但在這裏。

屬性類:

/// <summary> 
///  Attribute that signals that a dependency should be injected. 
/// </summary> 
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 
public sealed class InjectDependencyAttribute : Attribute 
{ 
    /// <summary> 
    ///  Initializes a new instance of the <see cref = "InjectDependencyAttribute" /> class. 
    /// </summary> 
    public InjectDependencyAttribute() 
    { 
     this.PreserveExistingValue = false; 
    } 

    /// <summary> 
    /// Gets or sets a value indicating whether to preserve an existing non-null value. 
    /// </summary> 
    /// <value> 
    /// <c>true</c> if the injector should preserve an existing value; otherwise, <c>false</c>. 
    /// </value> 
    public bool PreserveExistingValue { get; set; } 
} 

噴油器類:

public class AttributedDependencyInjector 
{ 
    /// <summary> 
    /// The component context. 
    /// </summary> 
    private readonly IComponentContext context; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="AttributedDependencyInjector"/> class. 
    /// </summary> 
    /// <param name="context">The context.</param> 
    public AttributedDependencyInjector(IComponentContext context) 
    { 
     this.context = context; 
    } 

    /// <summary> 
    /// Injects dependencies into an instance. 
    /// </summary> 
    /// <param name="instance">The instance.</param> 
    public void InjectDependencies(object instance) 
    { 
     this.InjectAttributedFields(instance); 
     this.InjectAttributedProperties(instance); 
    } 

    /// <summary> 
    /// Gets the injectable fields. 
    /// </summary> 
    /// <param name="instanceType"> 
    /// Type of the instance. 
    /// </param> 
    /// <param name="injectableFields"> 
    /// The injectable fields. 
    /// </param> 
    private static void GetInjectableFields(
     Type instanceType, ICollection<Tuple<FieldInfo, InjectDependencyAttribute>> injectableFields) 
    { 
     const BindingFlags BindingsFlag = 
      BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly; 
     IEnumerable<FieldInfo> fields = instanceType.GetFields(BindingsFlag); 

     // fields 
     foreach (FieldInfo field in fields) 
     { 
      Type fieldType = field.FieldType; 

      if (fieldType.IsValueType) 
      { 
       continue; 
      } 

      // Check if it has an InjectDependencyAttribute 
      var attribute = field.GetAttribute<InjectDependencyAttribute>(false); 
      if (attribute == null) 
      { 
       continue; 
      } 

      var info = new Tuple<FieldInfo, InjectDependencyAttribute>(field, attribute); 
      injectableFields.Add(info); 
     } 
    } 

    /// <summary> 
    /// Gets the injectable properties. 
    /// </summary> 
    /// <param name="instanceType"> 
    /// Type of the instance. 
    /// </param> 
    /// <param name="injectableProperties"> 
    /// A list into which are appended any injectable properties. 
    /// </param> 
    private static void GetInjectableProperties(
     Type instanceType, ICollection<Tuple<PropertyInfo, InjectDependencyAttribute>> injectableProperties) 
    { 
     // properties 
     foreach (var property in instanceType.GetProperties(
      BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)) 
     { 
      Type propertyType = property.PropertyType; 

      // Can't inject value types 
      if (propertyType.IsValueType) 
      { 
       continue; 
      } 

      // Can't inject non-writeable properties 
      if (!property.CanWrite) 
      { 
       continue; 
      } 

      // Check if it has an InjectDependencyAttribute 
      var attribute = property.GetAttribute<InjectDependencyAttribute>(false); 
      if (attribute == null) 
      { 
       continue; 
      } 

      // If set to preserve existing value, we must be able to read it! 
      if (attribute.PreserveExistingValue && !property.CanRead) 
      { 
       throw new BoneheadedException("Can't preserve an existing value if it is unreadable"); 
      } 

      var info = new Tuple<PropertyInfo, InjectDependencyAttribute>(property, attribute); 
      injectableProperties.Add(info); 
     } 
    } 

    /// <summary> 
    /// Determines whether the <paramref name="propertyType"/> can be resolved in the specified context. 
    /// </summary> 
    /// <param name="propertyType"> 
    /// Type of the property. 
    /// </param> 
    /// <returns> 
    /// <c>true</c> if <see cref="context"/> can resolve the specified property type; otherwise, <c>false</c>. 
    /// </returns> 
    private bool CanResolve(Type propertyType) 
    { 
     return this.context.IsRegistered(propertyType) || propertyType.IsAssignableFrom(typeof(ILog)); 
    } 

    /// <summary> 
    /// Injects dependencies into the instance's fields. 
    /// </summary> 
    /// <param name="instance"> 
    /// The instance. 
    /// </param> 
    private void InjectAttributedFields(object instance) 
    { 
     Type instanceType = instance.GetType(); 

     // We can't get information about the private members of base classes through reflecting a subclass, 
     // so we must walk up the inheritance hierarchy and reflect at each level 
     var injectableFields = new List<Tuple<FieldInfo, InjectDependencyAttribute>>(); 
     var type = instanceType; 
     while (type != null) 
     { 
      GetInjectableFields(type, injectableFields); 
      type = type.BaseType; 
     } 

     // fields 
     foreach (var fieldDetails in injectableFields) 
     { 
      var field = fieldDetails.Item1; 
      var attribute = fieldDetails.Item2; 

      if (!this.CanResolve(field.FieldType)) 
      { 
       continue; 
      } 

      // Check to preserve existing value 
      if (attribute.PreserveExistingValue && (field.GetValue(instance) != null)) 
      { 
       continue; 
      } 

      object fieldValue = this.Resolve(field.FieldType, instanceType); 
      field.SetValue(instance, fieldValue); 
     } 
    } 

    /// <summary> 
    /// Injects dependencies into the instance's properties. 
    /// </summary> 
    /// <param name="instance"> 
    /// The instance. 
    /// </param> 
    private void InjectAttributedProperties(object instance) 
    { 
     Type instanceType = instance.GetType(); 

     // We can't get information about the private members of base classes through reflecting a subclass, 
     // so we must walk up the inheritance bierarchy and reflect at each level 
     var injectableProperties = new List<Tuple<PropertyInfo, InjectDependencyAttribute>>(); 
     var type = instanceType; 
     while (type != typeof(object)) 
     { 
      Debug.Assert(type != null, "type != null"); 
      GetInjectableProperties(type, injectableProperties); 
      type = type.BaseType; 
     } 

     // Process the list and inject properties as appropriate 
     foreach (var details in injectableProperties) 
     { 
      var property = details.Item1; 
      var attribute = details.Item2; 

      // Check to preserve existing value 
      if (attribute.PreserveExistingValue && (property.GetValue(instance, null) != null)) 
      { 
       continue; 
      } 

      var propertyValue = this.Resolve(property.PropertyType, instanceType); 
      property.SetValue(instance, propertyValue, null); 
     } 
    } 

    /// <summary> 
    /// Resolves the specified <paramref name="propertyType"/> within the context. 
    /// </summary> 
    /// <param name="propertyType"> 
    /// Type of the property that is being injected. 
    /// </param> 
    /// <param name="instanceType"> 
    /// Type of the object that is being injected. 
    /// </param> 
    /// <returns> 
    /// The object instance to inject into the property value. 
    /// </returns> 
    private object Resolve(Type propertyType, Type instanceType) 
    { 
     if (propertyType.IsAssignableFrom(typeof(ILog))) 
     { 
      return LogManager.GetLogger(instanceType); 
     } 

     return this.context.Resolve(propertyType); 
    } 
} 

擴展方法:

public static class RegistrationExtensions 
{ 
    /// <summary> 
    /// Injects dependencies into the instance's properties and fields. 
    /// </summary> 
    /// <param name="context"> 
    /// The component context. 
    /// </param> 
    /// <param name="instance"> 
    /// The instance into which to inject dependencies. 
    /// </param> 
    public static void InjectDependencies(this IComponentContext context, object instance) 
    { 
     Enforce.ArgumentNotNull(context, "context"); 
     Enforce.ArgumentNotNull(instance, "instance"); 

     var injector = new AttributedDependencyInjector(context); 
     injector.InjectDependencies(instance); 
    } 
} 
+0

非常感謝這個想法。我正在考慮做這樣的事情。我唯一的問題是我現在必須將我的IOC容器引入我的基類。它現在必須知道什麼是IOC容器才能解決諸如「ILog」之類的問題。我希望找到一個解決方案,讓我的基類不知道任何國際奧委會容器 - 但這可能是不可能的。 – 2012-02-21 23:21:47

+0

您可以將'AttributedDependencyInjector'注入到您的基類中,或者更好的方法是返回一個AspectFactory類,該類將返回適當的依賴項解析的方面屬性集合。 – 2012-02-22 08:06:40