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