How to inject dependency name as a constructor parameter
Asked Answered
S

2

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?

Statolatry answered 23/1, 2013 at 16:17 Comment(2)
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 about too much logging.Cleodel
@Cleodel It's a great article and yes, there is a possibility I need to revise the way I log.Statolatry
K
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>();
Kassia answered 23/1, 2013 at 16:52 Comment(6)
You may want to provide some of the detail from the link to avoid this answer becoming dead if the link dies.Wrest
@Peter I think the solution at the link only deals with constructor injection, not property injection.Statolatry
@Statolatry - that's correct. It was not clear from your question that the ILog recipient requires property injection. Any reason not to use ctor injection?Kassia
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.Statolatry
@Statolatry - 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?Kassia
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.Statolatry
M
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.

Morette answered 23/1, 2013 at 16:46 Comment(2)
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.Statolatry
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.Morette

© 2022 - 2024 — McMap. All rights reserved.