6

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?

Digbyswift
  • 10,310
  • 4
  • 38
  • 66
  • When you need the name of the class in the logger it is often an indication of doing too much logging in the application. Consider taking a good look at the design of your application. [This SO answer goes in detail](http://stackoverflow.com/a/9915056/264697) about too much logging. – Steven Jan 23 '13 at 16:46
  • 1
    @Steven It's a great article and yes, there is a possibility I need to revise the way I log. – Digbyswift Jan 23 '13 at 17:06

2 Answers2

4

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>();
Peter Lillevold
  • 33,668
  • 7
  • 97
  • 131
  • You may want to provide some of the detail from the link to avoid this answer becoming dead if the link dies. – Guvante Jan 23 '13 at 16:55
  • @Peter I think the solution at the link only deals with constructor injection, not property injection. – Digbyswift Jan 23 '13 at 17:00
  • @Digbyswift - that's correct. It was not clear from your question that the ILog recipient requires property injection. Any reason not to use ctor injection? – Peter Lillevold Jan 23 '13 at 18:24
  • From my understanding, property injection is used when a dependency is optional and/or can have a default. I believe this scenario fits this bill, where by default I would like to use the Log4NetAdapter but in some cases maybe use a more involved Adapter based on a custom logger. Also, I try to avoid cluttering the ctor parameter list unnecessarily although this is secondary. – Digbyswift Jan 23 '13 at 19:36
  • @Digbyswift - I've extended the sample module to also do property injection. As for scenarios where you would want a custom log... I really don't see it. Any more concrete thoughts? – Peter Lillevold Jan 23 '13 at 21:02
  • Thanks for the explanation and the expansion on the answer, it's greatly appreciated. You're probably right about the custom log not being necessary. – Digbyswift Jan 24 '13 at 10:53
1

You only use logName to effectively resolve by name an ILog, so why not just inject an ILog?

public class Log4NetAdapter : ILogger
{
    private readonly ILog _logger;

    public Log4NetAdapter(ILog logger)
    {
        _logger = logger;
    }

    ...
}

OK, so now I've just moved the problem a bit, but I've also made this less coupled to other classes, namely the LogManager.

So if I was using unity, I would then do this to ensure I get the right logger:

var childContainer = container.CreateChildContainer();
childContainer.RegisterInstance<ILog>(LogManager.GetLogger(logName));
var adaptor = childContainer.Resolve<Log4NetAdapter>();

The child container prevents any other code getting access to that ILog. You can do this as high up as you like, I don't know any more about your code.

weston
  • 54,145
  • 21
  • 145
  • 203
  • Thanks, but your answer doesn't tell me where I get the `logName` parameter from - unless I've missed something. Also, it is specifically the example using the adapter I'm interested in, but I see your point. – Digbyswift Jan 23 '13 at 16:57
  • At some point you will know the `logName`, I can't help you if not, but that is the point you should register it as the `ILog` before resolving your `Log4NetAdapter` but I don't have any more code to go on so I can't advise any further. Looks like you got a solution though from Peter. – weston Jan 24 '13 at 15:13