c# - 如何在 .net 属性上注入(inject)属性依赖项?

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

我正在尝试使用本土类型的“方面”(实际上是 .net 属性)来应用一些行为。我有一个基类 (BankingServiceBase),它在启动时反射(reflect)自身以查看应用了哪些“方面”。然后它可以在操作之前或之后执行自定义行为。我正在使用 Autofac 作为我的 IOC 容器。我正在尝试应用 PropertiesAutowired方面的注册方法。在下面的示例代码中,我希望 Autofac 将 ILog 实例注入(inject)到我的方面/属性中。然而,它并没有这样做。我的猜测是,当我调用 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);
        }
    }
}

最佳答案

GetCustomAttributes 没有通过 Autofac 解析自定义属性是正确的 - 如果您考虑一下,FCL 代码(例如 GetCustomAttributes)怎么会知道 Autofac?自定义属性实际上是从程序集元数据中检索的,因此它们永远不会经过 Autofac 的解析过程,因此永远不会使用您的注册码。

您可以做的是自己将服务注入(inject)到属性实例中。从 Oliver's answer 中的代码开始生成方面属性列表。但是,在返回列表之前,您可以处理每个属性并将服务注入(inject)到任何依赖字段和属性中。我有一个名为 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; }
}

注入(inject)器类:

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

关于c# - 如何在 .net 属性上注入(inject)属性依赖项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9386193/

相关文章:

c# - 有没有办法控制 Dependency Property Binding 顺序值的更新?

c# - 如何固定字节数组?

java - 使用 Guice,在子类中注入(inject)依赖项

html - 更改 data-bg 中图像的 CSS

c# - 在 Dapper.NET 中使用 varbinary 参数

c# - 空条件运算符兼容性

c# - 如何确定在 Simple Injector 中使用哪种生活方式

JavaFX+Afterburner.fx从子窗口访问父窗口

javascript - 如何从列表中获取每个元素的数据属性

通过自定义属性解析 boost Spirit 规则