如何将依赖项名称注入构造函数参数

6

使用 Autofac,我可以使用以下代码将类注册为通过属性注入解析接口:

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

然而,我的Log4NetAdapter类有一个构造函数参数,需要调用类的名称。这样,我就可以根据调用类的名称记录事件。

public class Log4NetAdapter : ILogger
{
    private readonly ILog _logger;

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

    ...
}

如果每个依赖都有自己的Log4NetAdapter实例,我该如何将依赖的名称(即typeof(dependency).Name)注入到属性注入类的构造函数中?


当你需要在记录器中使用类的名称时,这通常表明应用程序中进行了过多的日志记录。请考虑仔细查看您的应用程序设计。此SO答案详细介绍有关过多日志记录的问题。 - Steven
1
@Steven 这是一篇很棒的文章,是的,我有可能需要修改我的日志记录方式。 - Digbyswift
2个回答

4

更新: 基于LogInjectionModule示例和Autofac如何进行属性注入,我已经扩展了该模块以实现构造函数和属性注入。

注意:我已经在OnComponentPreparing中修复了传递给LogManager的类型,以使用声明类型。这使得例如Resolve<Func<Service>>使用正确的日志类型。

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

关于如何使用该模块,这里是一个示例:

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

你可能想从链接中提供一些细节,以避免该答案在链接失效时变得无用。 - Guvante
@Peter 我认为链接中的解决方案只涉及构造函数注入,而不是属性注入。 - Digbyswift
@Digbyswift - 是的,你说得对。从你的问题中并不清楚ILog接收者需要属性注入。有什么理由不使用构造函数注入呢? - Peter Lillevold
据我理解,当依赖项是可选的和/或可以有默认值时,使用属性注入。我认为这种情况符合要求,其中默认情况下我想使用Log4NetAdapter,但在某些情况下可能会使用基于自定义记录器的更复杂的适配器。此外,我尽量避免不必要地使构造函数参数列表混乱,尽管这是次要的。 - Digbyswift
@Digbyswift - 我已经扩展了示例模块,使其也能进行属性注入。至于你想要自定义日志的情况...我真的看不出来。还有更具体的想法吗? - Peter Lillevold
感谢您的解释和答案的扩展,非常感谢。您可能是正确的,自定义日志可能并不必要。 - Digbyswift

1

你只需要使用logName来有效地通过名称解析ILog,那么为什么不直接注入一个ILog

public class Log4NetAdapter : ILogger
{
    private readonly ILog _logger;

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

    ...
}

好的,现在我只是稍微移动了一下问题,但也使其与其他类(即LogManager)的耦合性降低了。

所以如果我正在使用Unity,那么我将执行以下操作以确保获取正确的记录器:

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

子容器防止其他代码访问该ILog。您可以在任何高度上执行此操作,我不了解您的代码更多信息。


谢谢,但是你的回答没有告诉我从哪里获取logName参数 - 除非我漏掉了什么。另外,我特别感兴趣的是使用适配器的示例,但我理解你的观点。 - Digbyswift
在某个时刻,您将会知道 logName,如果不知道,我无法帮助您,但这是您在解析 Log4NetAdapter 之前应该将其注册为 ILog 的时机,但是我没有更多的代码可以参考,所以无法提供进一步的建议。看起来您已经从 Peter 那里得到了一个解决方案。 - weston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接