Search code examples
c#.netautofacproperty-injection

How to inject dependency name as a constructor parameter


Using Autofac, I can register a class to resolve against an interface using property injection, using the following code:

builder.RegisterType<Log4NetAdapter>()
       .As<ILogger>()
       .PropertiesAutowired()
       .InstancePerDependency();

However, my Log4NetAdapter class has a constructor parameter that requires the name of the calling class. This way, I can log events based upon the name of the calling class.

public class Log4NetAdapter : ILogger
{
    private readonly ILog _logger;

    public Log4NetAdapter(string logName)
    {
        _logger = LogManager.GetLogger(logName);
    }

    ...
}

How can I inject the name (i.e. typeof(dependency).Name) of the dependency into the property-injected class' constructor given that each dependency will have its own Log4NetAdapter instance?


Solution

  • Update: Building on the LogInjectionModule sample and how Autofac does property injection, I have extended the module to do both constructor and property injection.

    Note: I've fixed the type passed to LogManager in OnComponentPreparing to use the declaring type. This makes e.g. Resolve<Func<Service>> use the correct log type.

        using System.Linq;
        using log4net;
    
        public class LogInjectionModule : Module
        {
            protected override void AttachToComponentRegistration(IComponentRegistry registry, IComponentRegistration registration)
            {
                registration.Preparing += OnComponentPreparing;
                registration.Activating += OnComponentActivating;
            }
    
            private static void OnComponentActivating(object sender, ActivatingEventArgs<object> e)
            {
                InjectLogProperties(e.Context, e.Instance, false);
            }
    
            private static void OnComponentPreparing(object sender, PreparingEventArgs e)
            {
                e.Parameters = e.Parameters.Union(new[]
                    {
                        new ResolvedParameter(
                           (p, i) => p.ParameterType == typeof(ILog),
                           (p, i) => LogManager.GetLogger(p.Member.DeclaringType))
                    });
            }
    
            private static void InjectLogProperties(IComponentContext context, object instance, bool overrideSetValues)
            {
                if (context == null) throw new ArgumentNullException("context");
                if (instance == null) throw new ArgumentNullException("instance");
    
                var instanceType = instance.GetType();
                var properties = instanceType
                    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                    .Where(pi => pi.CanWrite && pi.PropertyType == typeof(ILog));
    
                foreach (var property in properties)
                {
                    if (property.GetIndexParameters().Length != 0)
                        continue;
    
                    var accessors = property.GetAccessors(false);
                    if (accessors.Length == 1 && accessors[0].ReturnType != typeof(void))
                        continue;
    
                    if (!overrideSetValues &&
                        accessors.Length == 2 &&
                        (property.GetValue(instance, null) != null))
                        continue;
    
                    ILog propertyValue = LogManager.GetLogger(instanceType);
                    property.SetValue(instance, propertyValue, null);
                }
            }
        }
    

    On how to use the module, here's a sample:

    public class Service
    {
        public Service(ILog log) { ... }
    }
    
    var cb = new ContainerBuilder();
    cb.RegisterModule<LogInjectionModule>();
    cb.RegisterType<Service>();
    var c = cb.Build();
    
    var service = c.Resolve<Service>();