如何在.net属性上注入属性依赖?

时间:2012-02-21 22:20:40

标签: c# dependency-injection attributes inversion-of-control autofac

我正在尝试使用本土类型的“方面”来应用某些行为,实际上是.net属性。我有一个基类(BankingServiceBase),它在启动时反映自己,以查看应用了哪些“方面”。然后,它可以在操作之前或之后执行自定义行为。我正在使用Autofac作为我的IOC容器。我正在尝试将PropertiesAutowired方法应用于方面的注册。在下面的示例代码中,我希望Autofac向我的aspect / attribute注入ILog实例。然而,它并没有这样做。我的猜测是,当我调用GetCustomAttributes时,它正在创建一个新实例,而不是从Autofac获取已注册的实例。思考?以下是一些可用的示例代码来显示问题:

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

2 个答案:

答案 0 :(得分:4)

你是正确的GetCustomAttributes无法通过Autofac解析自定义属性 - 如果你考虑一下,FCL代码如GetCustomAttributes如何知道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);
    }
}

答案 1 :(得分: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));
...