【问题标题】:Passing in the type of the declaring class for NLog using Autofac使用 Autofac 传入 NLog 的声明类的类型
【发布时间】:2011-07-08 10:51:03
【问题描述】:

this question 开始,我希望 autofac 将声明对象的类型注入到我的 NLog 服务的构造函数中,以便它可以正确记录哪种类型正在记录条目。

我的 NLogService 类看起来像这样...

public class NLogService : ILogService
{
    private readonly Logger _logger;

    public NLogService(Type t)
    {
        var consumerType = t.DeclaringType.FullName;
        _logger = LogManager.GetLogger(consumerType);
    }

但是它在应用程序启动时失败,因为它显然无法确定将什么注入到 NLogService 的构造函数中,并出现以下错误...

没有找到任何构造函数 类型上的“公共绑定标志” 'MyProduct.Domain.Services.Logging.NLogService' 可以使用可用的调用 服务和参数:不能 解析参数'System.Type t' 构造函数'Void .ctor(System.Type)'。

所以,我的问题是 - 我如何指示 autofac 注入调用类的类型?

我试过了……

public NLogService(Type t)
    {
        var method = MethodBase.GetCurrentMethod();
        Type consumingType = method.DeclaringType;
        var consumerType = consumingType.FullName;
        var consumerType = t.DeclaringType.FullName;
        _logger = LogManager.GetLogger(consumerType);
    }

但我最终得到的是MyProduct.Domain.Services.Logging.NLogService

我想要的是执行实际日志记录的类的类型。

我已经有tried this suggestion,但它对我也不起作用。

【问题讨论】:

  • 嗨 - 我已经尝试修复你链接到的另一个答案 - stackoverflow.com/questions/4774286/… - 如果你有机会尝试一下,你正在寻找的类型现在应该会自动注入.干杯!
  • 嗨,尼古拉斯,我会试试的。如果它现在确实有效,它肯定会“更干净”。谢谢!

标签: dependency-injection autofac nlog


【解决方案1】:

可以使您的 NLogService 通用,即 NLogService<T> 并使用 Autofac 的 open generics support

那么你可以这样做:

public class NLogService<T> : ILogger<T>
{
    private readonly Logger _logger;
    public NLogService()
    {
        _logger = LogManager.GetLogger(typeof(T).FullName);
    }
}

【讨论】:

【解决方案2】:

Autofac 没有真正的好方法,因为不支持“基于上下文的注入”(这是您正在尝试做的)。有一种解决方法,但它并不漂亮......

您可以做的是恢复到属性注入并为ILogService 属性定义一个基类或接口。例如,您可以定义以下接口:

public interface ILoggerContainer
{
    public ILogService Logger { get; set; }
}

现在您可以在所有需要记录器的类型上实现此接口:

public class Consumer : IConsumer, ILoggerContainer
{
    public ILogService Logger { get; set; }
}

有了这个,你可以按如下方式配置 Autofac:

builder.RegisterType<ILoggerContainer>()
    .OnActivating(e =>
{
    var type = typeof(LogService<>)
        .MakeGenericType(e.Instance.GetType());
    e.Instance.Logger = e.Context.Resolve(type);
});

另一种解决方法是注入一个与父类型相同类型的ILogger&lt;T&gt;

public class Consumer : IConsumer
{
    public Consumer(ILogger<Consumer> logger) { }
}

这使配置变得更加容易,并避免您必须拥有一个基类。哪个最合适由您决定。

正如我所说,这些都是变通方法,但老实说,您可能需要重新考虑应用程序中的日志记录策略。也许您在太多地方登录。在我编写的应用程序中,几乎不需要记录,当我这样做时,我会编写一个足够表达的记录消息,因此无需传达触发事件的类型。并且当您记录异常时,您将始终拥有完整的堆栈跟踪(并且异常记录几乎应该只发生在应用程序的外层,而不是无论如何都不会发生在服务中)。

【讨论】:

  • 感谢您的信息和建议
【解决方案3】:

根据我们的经验,以下技术效果很好:

  1. 创建一个如下所示的属性,它可以应用于类级别或注入站点:

    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class)]
    public class LoggerAttribute : Attribute
    {
        public readonly string Name;
    
        public LoggerAttribute(string name)
        {
            Name = name;
        }
    }
    
  2. 创建一个使用 ContainerBuilder 注册的 Autofac 模块:

    public class LogInjectionModule : Module
    {
        protected override void AttachToComponentRegistration(IComponentRegistry registry, IComponentRegistration registration)
        {
            registration.Preparing += OnComponentPreparing;
        }
    
        static void OnComponentPreparing(object sender, PreparingEventArgs e)
        {
            var typePreparing = e.Component.Activator.LimitType;
    
            // By default, the name supplied to the logging instance is the name of the type in which it is being injected into.
            string loggerName = typePreparing.FullName;
    
            //If there is a class-level logger attribute, then promote its supplied name value instead as the logger name to use.
            var loggerAttribute = (LoggerAttribute)typePreparing.GetCustomAttributes(typeof(LoggerAttribute), true).FirstOrDefault();
            if (loggerAttribute != null)
            {
                loggerName = loggerAttribute.Name;
            }
    
            e.Parameters = e.Parameters.Union(new Parameter[]
            {
                new ResolvedParameter(
                    (p, i) => p.ParameterType == typeof (Logger),
                    (p, i) =>
                    {
                        // If the parameter being injected has its own logger attribute, then promote its name value instead as the logger name to use.
                        loggerAttribute = (LoggerAttribute)
                        p.GetCustomAttributes(typeof(LoggerAttribute),true).FirstOrDefault();
                        if (loggerAttribute != null)
                        {
                            loggerName = loggerAttribute.Name;
                        }
    
                        // Return a new Logger instance for injection, parameterised with the most appropriate name which we have determined above.
                        return LogManager.GetLogger(loggerName);
                    }),
    
                // Always make an unamed instance of Logger available for use in delegate-based registration e.g.: Register((c,p) => new Foo(p.TypedAs<Logger>())
                new TypedParameter(typeof(Logger), LogManager.GetLogger(loggerName))
            });
        }
    }
    
  3. 您现在可以根据具体情况以以下任何一种方式注入命名记录器:

    • 默认情况下,注入的记录器名称将被赋予它注入的类的完整类型名称:

      public class Foo
      {
          public Foo(Logger logger)
          {
          }
      }
      
    • 使用构造函数参数 [Logger] 属性覆盖记录器名称:

      public class Foo
      {
          public Foo([Logger("Meaningful Name")]Logger logger)
          {
          }
      }
      
    • 使用类级别的 [Logger] 属性为所有构造函数重载设置相同的记录器名称覆盖:

      [Logger("Meaningful Name")]
      public class Foo
      {
          public Foo(Logger logger, int something)
          {
          }
      
          public Foo(Logger logger, int something, DateTime somethingElse)
          {
          }
      }
      
    • 在每个构造函数重载上使用构造函数参数 [Logger] 属性来根据构造的上下文设置不同的记录器名称:

      public class Foo
      {
          public Foo(Logger("Meaningful Name")]Logger logger, int something)
          {
          }
      
          public Foo(Logger("Different Name")]Logger logger, int something, DateTime somethingElse)
          {
          }
      }
      


重要提示:如果您使用 Autofac 的委托注册注册要通过记录器构造函数注入解析的类型,则必须使用两个参数重载,如下所示:Register((c,p) =&gt; new Foo(p.TypedAs&lt;Logger&gt;())

希望这可以帮助!

【讨论】:

  • 我正在尝试将 NLog 注入控制器。关于您的回答,我如何为每个控制器注册 Nlog?
  • 这取决于您的 Autofac 注册码。假设 NLog(或您的抽象)已经注册。然后,如果您通过RegisterAssemblyTypesRegisterType 使用基于反射的注册,那么NLog(或您的抽象)将被注入到指定为依赖项的任何位置。如果您使用的是基于委托的注册,那么您需要使用我上面提到的构造 Register((c,p) =&gt; new Foo(p.TypedAs&lt;Logger&gt;())
【解决方案4】:

没有泛型也可以做到这一点。

但是,请注意,在 Autofac 6.x 中,解析过程已更改为使用解析管道。这对于大多数情况无关紧要,但是当您想使用诸如OnPreparing 等生命周期事件时,它确实如此。关于覆盖Preparing 事件的大多数答案都非常陈旧,现在已经过时了。您不能再直接覆盖Preparing

在 Autofac 文档站点上有一个 example 为 log4net 执行此操作,它可以与 NLog 一起使用,只需稍作更改。基本思路如下:

public class Log4NetMiddleware : IResolveMiddleware
{
    public PipelinePhase Phase => PipelinePhase.ParameterSelection;

    public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
    {
        // Add our parameters.
        context.ChangeParameters(context.Parameters.Union(
            new[]
            {
              new ResolvedParameter(
                  (p, i) => p.ParameterType == typeof(ILog),
                  (p, i) => LogManager.GetLogger(p.Member.DeclaringType)
              ),
            }));

        // Continue the resolve.
        next(context);

        // Has an instance been activated?
        if (context.NewInstanceActivated)
        {
            var instanceType = context.Instance.GetType();

            // Get all the injectable properties to set.
            // If you wanted to ensure the properties were only UNSET properties,
            // here's where you'd do it.
            var properties = instanceType
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.PropertyType == typeof(ILog) && p.CanWrite && p.GetIndexParameters().Length == 0);

            // Set the properties located.
            foreach (var propToSet in properties)
            {
                propToSet.SetValue(context.Instance, LogManager.GetLogger(instanceType), null);
            }
        }
    }
}

还请注意,您必须了解 Autofac 中的中间件是如何工作的。 documentation 是一个很好的起点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2020-09-24
    • 2018-04-01
    相关资源
    最近更新 更多