Как работает модуль регистрации для 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
правильный?
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. Есть много документации, которая может ответить на вопросы.
- Модуль NLog здесь, кажется, основан на модуле log4net в документации, в которой есть немного больше объяснения того, что происходит.
- Там есть объяснение параметров (например,
TypedParameter
) и как они используются.
Учитывая, что документы могут завершить некоторые вещи, которые могут быть неясными, вместо того, чтобы пытаться рассмотреть каждый пункт "мои мысли верны", позвольте мне просто сильно аннотировать модуль и надеяться, что это прояснит ситуацию.
// 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, так это в способности вводить свойства для регистратора. Однако я не собираюсь решать это здесь; Вы можете посмотреть на пример прямо в документации и использовать его как упражнение, если вам нужна эта функциональность.
Надеюсь, это поможет. Я, вероятно, не вернусь, чтобы ответить на дополнительные вопросы, поэтому, если этого недостаточно, я очень, очень рекомендую установить некоторые контрольные точки, может быть, установить несколько крошечных тестов с минимальным повторением, и тому подобное, и сделать более глубокое исследование, чтобы получить ясность. Честно говоря, одно дело, когда кто-то другой объясняет это, но другое - увидеть это в действии и погрузиться в источник различных проектов. С последним подходом вы получите более полное понимание, даже если оно потенциально не так быстро.