Передача типа декларирующего класса для NLog с использованием Autofac - PullRequest
4 голосов
/ 08 июля 2011

Исходя из этого вопроса Я бы хотел, чтобы 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

То, что мне нужно, это тип класса, который ведет реальное ведение журнала.

я имею уже попробовал это предложение , и оно у меня тоже не сработало.

Ответы [ 3 ]

6 голосов
/ 08 июля 2011

Нет действительно хорошего способа сделать это с 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<T> того же типа, что и тип родительского типа:

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

Это делаетконфигурация намного проще и не позволяет иметь базовый класс.Какой из них наиболее уместен, решать только вам.

Как я уже сказал, это обходные пути, но, если честно, вам, возможно, придется пересмотреть свою стратегию ведения журналов в вашем приложении.Возможно, вы заходите в слишком много мест.В приложениях, которые я пишу, едва ли когда-либо возникает необходимость регистрироваться, и когда я это делаю, я пишу сообщение регистрации, которое достаточно выразительно, чтобы не было необходимости сообщать тип, вызвавший событие.И когда вы регистрируете исключение, у вас всегда будет полная трассировка стека (а ведение журнала исключений должно происходить почти только на внешнем уровне вашего приложения, а не в службах в любом случае).

6 голосов
/ 08 июля 2011

Не могли бы сделать ваш NLogService универсальным, то есть NLogService<T> и использовать поддержку открытых обобщений Autofac ?

Тогда вы можете сделать это:

public class NLogService<T> : ILogger<T>
{
    private readonly Logger _logger;
    public NLogService()
    {
        _logger = LogManager.GetLogger(typeof(T).FullName);
    }
}
2 голосов
/ 17 августа 2012

В нашем опыте хорошо работает следующая техника:

  1. Создайте атрибут, подобный приведенному ниже, который можно применять на уровне класса или на сайте инъекции:

    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class)]
    public class LoggerAttribute : Attribute
    {
        public readonly string Name;
    
        public LoggerAttribute(string name)
        {
            Name = name;
        }
    }
    
  2. Создайте модуль Autofac, который вы зарегистрируете с помощью ContainerBuilder:

    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) => new Foo(p.TypedAs<Logger>()).

Надеюсь это поможет!

...