Как работает модуль регистрации для Autofac и NLog?

Я все еще довольно новичок в Autofac и Nlog, и мне нужна помощь в понимании того, что происходит в моем модуле Autofac LoggingModule для Nlog. Он работает, как и ожидалось, благодаря использованию /questions/45380500/vnedrenie-nlog-s-pomoschyu-autofacs-registergeneric. Но вместо того, чтобы просто копировать вставку, я хотел бы убедиться, что я понимаю, что происходит в каждом методе (Load & AttachToComponentRegistration). Если бы вы могли пересмотреть мои мысли и уточнить, что я ошибаюсь (я уверен, что это немного), я был бы очень признателен. Заранее спасибо!

  • Цель базы данных с использованием Nlog
  • Внедрение зависимостей с использованием Autofac
  • ASP.NET MVC веб-приложение для обучения
  • Приложение Dvd Libary (DvdAdd, DvdEdit, DvdDelete, DvdList)

LoggingModule

public class LoggingModule : Module
{

    protected override void Load(ContainerBuilder builder)
    {
        builder
            .Register((c, p) => new LogService(p.TypedAs<Type>()))
            .AsImplementedInterfaces();
    }

    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
    {
        registration.Preparing +=
            (sender, args) =>
            {
                var forType = args.Component.Activator.LimitType;

                var logParameter = new ResolvedParameter(
                    (p, c) => p.ParameterType == typeof(ILog),
                    (p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));

                args.Parameters = args.Parameters.Union(new[] { logParameter });
            };
    }

}

Мое понимание кода внутри Load()

c - Параметр c, предоставленный выражению, является контекстом компонента (объект IComponentContext), в котором создается компонент. Контекст, в котором можно получить доступ к сервису или разрешить зависимости компонента.

p - IEnumerable с набором входящих параметров

AsImplementedInterfaces - Autofac позволяет пользователям явно или неявно регистрировать типы. Хотя " As " используется для явных регистраций, " AsImplementedInterfaces " и " AsSelf " используются для неявных. Другими словами, контейнер автоматически регистрирует реализацию для всех интерфейсов, которые он реализует.

Мысли: код метода Load регистрирует новый класс LogService (который представляет " c ") с типом logger (который представляет " p ") в качестве параметра конструктора для класса LogService

Вопросы:

  • Мои мысли выше верны?
  • Должно ли это быть SingleInstance или оно будет / будет жить так долго, как область видимости вызывающих классов? (Я думаю о своей единице работы)

Мое понимание кода внутри AttachToComponentRegistration()

Метод AttachToComponentRegistration - переопределение для присоединения функциональности, специфичной для модуля, к регистрации компонента.

Параметры AttachToComponentRegistration:

  • IComponentRegistry componentRegistry - обеспечивает регистрации компонентов в соответствии с услугами, которые они предоставляют.
  • Регистрация IComponentRegistration - описывает логический компонент в контейнере.

registration.Preparing - срабатывает, когда требуется новый экземпляр. Экземпляр может быть предоставлен, чтобы пропустить обычный активатор, установив свойство Instance в предоставленных аргументах события.


var forType = args.Component.Activator.LimitType;

args = Autofac.Core.PreparingEventArgs - запускается перед процессом активации, чтобы разрешить изменение параметров или предоставление альтернативного экземпляра.

Component = PreparingEventArgs.Component Свойство - получает компонент, обеспечивающий активируемый экземпляр

Activator = IComponentRegistration.Activator Свойство - получает активатор, используемый для создания экземпляров.

LimitType = IInstanceActivator.LimitType Свойство - получает наиболее конкретный тип, к которому, как известно, могут быть преобразованы экземпляры компонента.

Мысли о forType - Насколько я понимаю, эта переменная содержит Name а также FullName вызывающего класса, откуда вызывается служба журналирования?

Изображение отладчика forType

Вопросы:

  • Мои мысли forType правильный?

var logParameter = new ResolvedParameter(
                    (p, c) => p.ParameterType == typeof(ILog),
                    (p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));

ResolvedParameter - может использоваться как способ предоставления значений, динамически извлекаемых из контейнера, например, путем разрешения службы по имени.

Мысли о logParameter - Здесь я начинаю заблудиться. Таким образом, он проверяет, что параметр имеет тип ILog, и если это так, то он разрешает его с помощью параметра конструктора и передает forType переменная?

Вопросы:

  • Мои мысли о logParameter выше правильно?

args.Parameters = args.Parameters.Union(new[] { logParameter });

args.Parameters = PreparingEventArgs.Parameters Property - Получает или задает параметры, предоставленные активатору.

args.Parameters.Union = Создает объединение двух последовательностей, используя компаратор равенства по умолчанию. Возвращает System.Collections.Generic.IEnumerable`1, который содержит элементы из обеих входных последовательностей, за исключением дубликатов.

Мысли о args.Parameters - Я действительно не знаю в этот момент, кроме как догадаться, что он возвращает коллекцию параметров и удаляет дубликаты?

Вопросы:

  • Не могли бы вы помочь мне рассказать, что происходит в args.Parameters?

Изображение отладчика logParameter Изображение таблицы базы данных Nlog


LogService класс

public class LogService : ILog
{
    private readonly ILogger _log;

    public LogService(Type type)
    {
        _log = LogManager.GetLogger(type.FullName);
    }

    public void Debug(string message, params object[] args)
    {
        Log(LogLevel.Debug, message, args);
    }

    public void Info(string message, params object[] args)
    {
        Log(LogLevel.Info, message, args);
    }

    public void Warn(string message, params object[] args)
    {
        Log(LogLevel.Warn, message, args);
    }

    public void Error(string message, params object[] args)
    {
        Log(LogLevel.Error, message, args);
    }

    public void Error(Exception ex)
    {
        Log(LogLevel.Error, null, null, ex);
    }

    public void Error(Exception ex, string message, params object[] args)
    {
        Log(LogLevel.Error, message, args, ex);
    }

    public void Fatal(Exception ex, string message, params object[] args)
    {
        Log(LogLevel.Fatal, message, args, ex);
    }

    private void Log(LogLevel level, string message, object[] args)
    {
        _log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args));
    }

    private void Log(LogLevel level, string message, object[] args, Exception ex)
    {
        _log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args, ex));
    }

}

Интерфейс ILog

public interface ILog
{
    void Debug(string message, params object[] args);

    void Info(string message, params object[] args);

    void Warn(string message, params object[] args);


    void Error(string message, params object[] args);
    void Error(Exception ex);

    void Error(Exception ex, string message, params object[] args);

    void Fatal(Exception ex, string message, params object[] args);
}

1 ответ

Решение

Здесь есть что распаковать. Вы на самом деле не спрашиваете ответ на конкретный вопрос, а скорее описываете код и объясняете существующее решение, которое работает, поэтому я мог бы предложить опубликовать в StackExchange Code Review, если вам нужно гораздо больше, чем я собираюсь дать ты здесь. Не пытаясь быть бесполезным, но, как если бы ваш вопрос: "Правильно ли я думаю?" и ответ "своего рода", есть много дискуссий по каждой отдельной точке, чтобы объяснить, почему "своего рода" является ответом (или "нет", или "да", в зависимости от обстоятельств). Это может превратиться в длинный ответ, за которым последуют дополнительные вопросы для уточнения, которые требуют еще дополнительных ответов... и Stackru на самом деле не является дискуссионным форумом, способным на подобные вещи.

[то есть, я, вероятно, потрачу час и напишу здесь ответ... но я не могу обещать, что на самом деле вернусь, чтобы что-то обсудить, потому что есть другие вопросы, на которые нужно ответить, и другие вещи, которые мне нужно выделить время. Stackru действительно больше о "Как я...?" или другие вещи, которые имеют один, достаточно конкретный ответ.]

Во-первых, я рекомендую погрузиться в себя с отладчиком на некоторых точках останова, чтобы реально увидеть, что происходит. Например, вы спросили, что в LimitType в одной области - вы можете довольно легко ответить на этот вопрос, просто вставив точку останова на этой строке и посмотрев на значение. Это будет хорошим способом следить за дополнительными разъяснениями самостоятельно - контрольными точками для победы.

Во-вторых, я рекомендую провести некоторое время с документами Autofac. Есть много документации, которая может ответить на вопросы.

Учитывая, что документы могут завершить некоторые вещи, которые могут быть неясными, вместо того, чтобы пытаться рассмотреть каждый пункт "мои мысли верны", позвольте мне просто сильно аннотировать модуль и надеяться, что это прояснит ситуацию.

// General module documentation is here:
// https://autofac.readthedocs.io/en/latest/configuration/modules.html
public class LoggingModule : Module
{
  // Load basically registers types with the container just like
  // if you were doing it yourself on the ContainerBuilder. It's
  // just a nice way of packaging up a set of registrations so
  // they're not all in your program's "Main" method or whatever.
  protected override void Load(ContainerBuilder builder)
  {
    // This is a lambda registration. Docs here:
    // https://autofac.readthedocs.io/en/latest/register/registration.html#lambda-expression-components
    // This one uses both the component context (c) and the incoming
    // set of parameters (p). In this lambda, the parameters are NOT the set of constructor
    // parameters that Autofac has resolved - they're ONLY things that
    // were MANUALLY specified. In this case, it's assuming a TypedParameter
    // with a System.Type value is being provided manually. It's not going
    // to try resolving that value from the container. This is going hand-in-hand
    // with the logParameter you see in AttachToComponentRegistration.
    // Parameter docs are here:
    // https://autofac.readthedocs.io/en/latest/resolve/parameters.html
    // In general if you resolve something that has both manually specified parameters
    // and things that can be resolved by Autofac, the manually specified parameters
    // will take precedence. However, in this lambda it's very specifically looking
    // for a manually specified parameter.
    // You'll want to keep this as a default InstancePerDependency because you probably
    // want this to live as long as the thing using it and no longer. Likely
    // NLog already has object pooling and caching built in so this isn't as
    // expensive as you think, but I'm no NLog expert. log4net does handle
    // that for you.
    builder
      .Register((c, p) => new LogService(p.TypedAs<Type>()))
      .AsImplementedInterfaces();
  }

  // This method attaches a behavior (in this case, an event handler) to every
  // component registered in the container. Think of it as a way to run a sort
  // of "global foreach" over everything registered.
  protected override void AttachToComponentRegistration(
    IComponentRegistry componentRegistry,
    IComponentRegistration registration)
  {
    // The Preparing event is called any time a new instance is needed. There
    // are docs for the lifetime events but Preparing isn't on there. Here are the
    // docs and the issue I filed on your behalf to get Preparing documented.
    // https://autofac.readthedocs.io/en/latest/lifetime/events.html
    // https://github.com/autofac/Documentation/issues/69
    // You can see the Preparing event here:
    // https://github.com/autofac/Autofac/blob/6dde84e5b0a3f82136a0567a84da498b04e1fa2d/src/Autofac/Core/IComponentRegistration.cs#L83
    // and the event args here:
    // https://github.com/autofac/Autofac/blob/6dde84e5b0/src/Autofac/Core/PreparingEventArgs.cs
    registration.Preparing +=
      (sender, args) =>
        {
          // The Component is the thing being resolved - the thing that
          // needs a LogService injected. The Component.Activator is the
          // thing that is actually going to execute to "new up" an instance
          // of the Component. The Component.Activator.LimitType is the actual
          // System.Type of the thing being resolved.
          var forType = args.Component.Activator.LimitType;

          // The docs above explain ResolvedParameter - basically a manually
          // passed in parameter that can execute some logic to determine if
          // it satisfies a constructor or property dependency. The point of
          // this particular parameter is to provide an ILog to anything being
          // resolved that happens to have an ILog constructor parameter.
          var logParameter = new ResolvedParameter(

            // p is the System.Reflection.ParameterInfo that describes the
            // constructor parameter that needs injecting. c is the IComponentContext
            // in which the resolution is being done (not used here). If this
            // method evaluates to true then this parameter will be used; if not,
            // it will refuse to provide a value. In this case, if the parameter
            // being injected is an ILog, this ResolvedParameter will tell Autofac
            // it can provide a value.
            (p, c) => p.ParameterType == typeof(ILog),

            // p and c are the same here, but this time they're used to actually
            // generate the value of the parameter - the ILog instance that should
            // be injected. Again, this will only run if the above predicate evaluates
            // to true. This creates an ILog by manually resolving from the same
            // component context (the same lifetime scope) as the thing that
            // needs the ILog. Remember earlier that call to p.AsTyped<Type>()
            // to get a parameter? The TypedParameter thing here is how that
            // value gets poked in up there. This Resolve call will effectively
            // end up calling the lambda registration.
            (p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));

          // The thing being resolved (the component that consumes ILog) now
          // needs to be told to make use of the log parameter, so add it into
          // the list of parameters that can be used when resolving that thing.
          // If there's an ILog, Autofac will use this specified parameter to
          // fulfill the requirement.
          args.Parameters = args.Parameters.Union(new[] { logParameter });
        };
    }
}

Чего не хватает в этом, что присутствует в примере модуля log4net, так это в способности вводить свойства для регистратора. Однако я не собираюсь решать это здесь; Вы можете посмотреть на пример прямо в документации и использовать его как упражнение, если вам нужна эта функциональность.

Надеюсь, это поможет. Я, вероятно, не вернусь, чтобы ответить на дополнительные вопросы, поэтому, если этого недостаточно, я очень, очень рекомендую установить некоторые контрольные точки, может быть, установить несколько крошечных тестов с минимальным повторением, и тому подобное, и сделать более глубокое исследование, чтобы получить ясность. Честно говоря, одно дело, когда кто-то другой объясняет это, но другое - увидеть это в действии и погрузиться в источник различных проектов. С последним подходом вы получите более полное понимание, даже если оно потенциально не так быстро.

Другие вопросы по тегам