How to enter property dependencies on .net attribute?

I am trying to apply some kind of behavior using the home type “aspect”, really the .net attribute. I have a base class ( BankingServiceBase ) that reflects on itself at startup to see which “aspects” apply to it. It can then perform custom behavior before or after operations. I use Autofac as my IOC container. I am trying to apply the PropertiesAutowired method to register an aspect. In the code example below, I want Autofac to inject an instance of ILog to my aspect / attribute. However, this does not. I assume that when I call GetCustomAttributes , it creates a new instance instead of getting the registered instance from Autofac. Thoughts? Here is an example of useful code to display the problem:

 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); } } } 
+4
source share
2 answers

Are you right that GetCustomAttributes does not allow custom attributes through Autofac - if you think about it, how does FCL code like GetCustomAttributes know about Autofac? Custom attributes are actually extracted from the assembly metadata, so they never go through the Autofac resolution process, so your registration code is never used.

What you can do is enter the services into the attribute instance yourself. Start with the code in the Oliver answer to create a list of aspect attributes. However, before returning the list, you can process each attribute and enter services in any dependent fields and properties. I have a class called AttributedDependencyInjector , which I use with the extension method. It uses reflection to scan fields and properties decorated with InjectDependencyAttribute , and then sets the value of these properties. There is quite a lot of code to deal with various scenarios, but here it is.

Attribute Class:

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

Injector Class:

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

Extension Method:

 public static class RegistrationExtensions { /// <summary> /// Injects dependencies into the instance 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); } } 
+4
source

Try lazy aspect loading

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

Then use it like this:

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

Source: https://habr.com/ru/post/1397682/


All Articles