Введення залежності та іменовані реєстратори


74

Мені цікаво дізнатись більше про те, як люди вводять реєстрацію за допомогою платформ для введення залежностей. Незважаючи на те, що посилання нижче та мої приклади стосуються log4net та Unity, я не обов'язково буду використовувати жоден із них. Для введення залежностей / IOC я, ймовірно, буду використовувати MEF, оскільки це стандарт, на якому зупиняється решта проекту (великий).

Я дуже новачок у введенні залежностей / ioc, і я дуже новачок у C # і .NET (написав дуже мало виробничого коду в C # /. NET після останніх 10 років або близько того VC6 і VB6). Я провів багато розслідувань щодо різноманітних рішень для ведення журналів, тому я думаю, що я гідно працюю з їх наборами функцій. Я просто недостатньо обізнаний з фактичною механікою введення однієї залежності (або, можливо, більш «правильно», введення абстрагованої версії однієї залежності).

Я бачив інші публікації, пов'язані з веденням журналу та / або ін'єкцією залежностей, наприклад: інтерфейси ін'єкції залежностей та реєстрації

Найкращі практики ведення журналів

Як би виглядав клас Log4Net Wrapper?

ще раз про log4net та Unity IOC config

Моє запитання не стосується конкретно "Як мені ввести платформу реєстрації xxx за допомогою інструменту ioc yyy?" Швидше, мене цікавить, як люди обробляли обтікання платформи реєстрації (як це часто, але не завжди рекомендується) та конфігурацію (тобто app.config). Наприклад, використовуючи log4net як приклад, я міг налаштувати (в app.config) кількість реєстраторів, а потім отримати ці реєстратори (без введення залежностей) стандартним способом використання коду, як це:

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

Як варіант, якщо мій реєстратор названий не для класу, а для функціональної області, я міг би зробити це:

private static readonly ILog logger = LogManager.GetLogger("Login");
private static readonly ILog logger = LogManager.GetLogger("Query");
private static readonly ILog logger = LogManager.GetLogger("Report");

Отже, я думаю, що мої "вимоги" були б приблизно такими:

  1. Я хотів би ізолювати джерело свого продукту від прямої залежності від лісозаготівельної платформи.

  2. Я хотів би мати можливість вирішити конкретний іменований екземпляр журналу (ймовірно, ділившись одним і тим же екземпляром серед усіх запитувачів того самого іменного екземпляра), прямо чи опосередковано за допомогою якоїсь ін’єкції залежностей, можливо, MEF.

  3. Я не знаю, чи назвав би це жорсткою вимогою, але я хотів би отримати можливість іменованого реєстратора (відмінного від реєстратора класів) на вимогу. Наприклад, я можу створити реєстратор для свого класу на основі назви класу, але одному методу потрібна детальна важка діагностика, яку я хотів би контролювати окремо. Іншими словами, я міг би хотіти, щоб один клас "залежав" від двох окремих екземплярів реєстратора.

Почнемо з цифри 1. Я прочитав низку статей, насамперед тут, про stackoverflow, про те, чи є це гарною ідеєю завертати. Дивіться посилання "найкращі практики" вище і перейдіть до коментаря Джеффрі Хантіна , щоб побачити одне уявлення про те, чому погано обертати log4net. Якби ви обгортали (і якщо могли б ефективно обертати), чи обгортали б ви суворо з метою ін’єкції / видалення прямої плідниці? Або ви також спробуєте абстрагувати частину або всю інформацію log4net app.config?

Скажімо, я хочу використовувати System.Diagnostics, я, мабуть, хотів би реалізувати реєстратор на основі інтерфейсу (можливо, навіть використовуючи "загальний" інтерфейс ILogger / ILog), ймовірно, на основі TraceSource, щоб я міг його ввести. Чи могли б ви реалізувати інтерфейс, скажімо через TraceSource, і просто використовувати інформацію System.Diagnostics app.config як є?

Щось на зразок цього:

public class MyLogger : ILogger
{
  private TraceSource ts;
  public MyLogger(string name)
  {
    ts = new TraceSource(name);
  }

  public void ILogger.Log(string msg)
  {
    ts.TraceEvent(msg);
  }
}

І використовуйте його так:

private static readonly ILogger logger = new MyLogger("stackoverflow");
logger.Info("Hello world!")

Перехід до номера 2 ... Як вирішити конкретний примірник журналу? Чи слід просто використовувати інформацію app.config вибраної мною платформи ведення журналу (тобто вирішувати реєстратори на основі схеми імен у app.config)? Отже, у випадку з log4net, чи можу я віддати перевагу "впорскуванню" LogManager (зауважте, що я знаю, що це неможливо, оскільки це статичний об'єкт)? Я міг би обернути LogManager (назвати його MyLogManager), надати йому інтерфейс ILogManager, а потім вирішити інтерфейс MyLogManager.ILogManager. Інші мої об'єкти можуть мати залежність (Імпорт мовою MEF) від ILogManager (Експорт із збірки, де він реалізований). Тепер я міг мати такі об’єкти:

public class MyClass
{
  private ILogger logger;
  public MyClass([Import(typeof(ILogManager))] logManager)
  {
    logger = logManager.GetLogger("MyClass");
  }
}

Кожного разу, коли викликається ILogManager, він безпосередньо делегує LogManager log4net. Як варіант, може загорнутий LogManager взяти екземпляри ILogger, які він отримує на основі app.config, та додати їх до (a?) Контейнера MEF за іменем. Пізніше, коли запитується журнал з тим самим іменем, обгортаний LogManager запитується щодо цього імені. Якщо там є ILogger, це вирішується таким чином. Якщо це можливо з MEF, чи є якась користь від цього?

У цьому випадку насправді «вводиться» лише ILogManager, і він може передавати екземпляри ILogger так, як це робить log4net. Як цей тип ін’єкції (по суті фабричної) порівнюється з ін’єкцією названих екземплярів реєстратора? Це дозволяє легше використовувати файл log.net (або іншу платформу ведення журналу) app.config.

Я знаю, що можу отримати іменовані екземпляри з контейнера MEF так:

var container = new CompositionContainer(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

Але як мені перенести названі екземпляри в контейнер? Я знаю про модель, засновану на атрибутах, де я міг би мати різні реалізації ILogger, кожна з яких називається (через атрибут MEF), але це насправді мені не допомагає. Чи є спосіб створити щось на зразок app.config (або розділу в ньому), який перелічував би реєстратори (всі однакові реалізації) за назвою та які MEF міг читати? Чи міг / повинен бути центральний "менеджер" (наприклад, MyLogManager), який вирішує іменовані реєстратори за допомогою базового app.config, а потім вставляє вирішений реєстратор у контейнер MEF? Таким чином він буде доступний комусь іншому, хто має доступ до того самого контейнера MEF (хоча без відома MyLogManager про те, як використовувати інформацію app.config log4net,

Це вже сталося досить довго. Я сподіваюся, що це послідовно. Будь ласка, не соромтеся поділитися будь-якою конкретною інформацією про те, як ви залежністю ввели платформу ведення журналу (ми, швидше за все, розглядаємо log4net, NLog або щось (сподіваємося, тонке), побудоване на System.Diagnostics) у вашу програму.

Ви ввели "менеджер" і він повернув екземпляри реєстратора?

Чи додали ви якусь власну інформацію про конфігурацію у своєму власному розділі конфігурації або в конфігураційному розділі вашої платформи DI, щоб полегшити / зробити можливим безпосереднє введення екземплярів реєстратора (тобто зробити так, щоб ваші залежності були на ILogger, а не на ILogManager).

А як щодо наявності статичного або глобального контейнера, в якому є або інтерфейс ILogManager, або набір примірників іменованих ILogger. Отже, замість впорскування в загальноприйнятому розумінні (за допомогою конструктора, властивості або даних-членів), залежність від реєстрації явно вирішується на вимогу. Це хороший чи поганий спосіб введення залежності.

Я позначаю це як спільноту wiki, оскільки це не здається запитанням із чіткою відповіддю. Якщо хтось відчуває інакше, сміливо змінюйте це.

Дякуємо за будь-яку допомогу!

Відповіді:


38

Я використовую Ninject для вирішення поточного імені класу для екземпляра реєстратора, як це:

kernel.Bind<ILogger>().To<NLogLogger>()
  .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName);

Конструктор реалізації NLog може виглядати так:

public NLogLogger(string currentClassName)
{
  _logger = LogManager.GetLogger(currentClassName);
}

Цей підхід, мабуть, повинен працювати і з іншими контейнерами МОК.


6
Раніше я x.Request.ParentContext.Plan.Type.FullNameотримував конкретну назву класу замість імені інтерфейсу.
Chadwick

Я завжди отримую ParentContext як null. У мене NLogLogger успадковується від Loggerbase, який реалізує ILogger.
Маршал

Якщо базовий клас знаходиться в іншому проекті, переконайтеся, що ви знову ініціалізуєте реєстратор, наприклад: [Assembly: log4net.Config.XmlConfigurator (Watch = true)]
Орхан,

16

Можна також використовувати Common.Logging фасад або Simple Logging Facade .

Обидва вони використовують шаблон стилю локатора служб для отримання ILogger.

Чесно кажучи, ведення журналу - це одна з тих залежностей, які я не бачу майже ніякого значення в автоматичному введенні.

Більшість моїх класів, для яких потрібні послуги ведення журналу, виглядають так:

public class MyClassThatLogs {
    private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName);

}

Використовуючи Simple Logging Facade, я переключив проект з log4net на NLog, і я додав ведення журналу із сторонньої бібліотеки, яка використовувала log4net на додаток до реєстрації мого додатка за допомогою NLog. Тобто фасад нам добре послужив.

Одним застереженням, якого важко уникнути, є втрата функцій, характерних для тієї чи іншої системи ведення журналу, мабуть, найпоширенішим прикладом цього є користувацькі рівні реєстрації.


Дякуємо за підказку щодо SLF та Common.Logging. Я насправді експериментував із цими бібліотеками нещодавно, і обидві вони дуже класні. Врешті-решт, я думаю, що наш проект, ймовірно, не буде використовувати DI для отримання журналу, тому ми можемо в кінцевому підсумку використовувати SLF або Common.Logging.
wageoghe

13

Це на користь тих, хто намагається зрозуміти, як внести залежність реєстратора, коли реєстратору, який ви хочете ввести, надається платформа реєстрації, така як log4net або NLog. Моя проблема полягала в тому, що я не міг зрозуміти, як я можу зробити клас (наприклад, MyClass) залежним від інтерфейсу типу ILogger, коли я знав, що роздільна здатність конкретного ILogger буде залежати від знання типу класу, який залежить від ILogger ( наприклад MyClass). Як платформа / контейнер DI / IoC отримує правильний ILogger?

Ну, я подивився джерело для Castle та NInject і побачив, як вони працюють. Я також переглянув AutoFac та StructureMap.

Castle і NInject забезпечують здійснення лісозаготівлі. Обидва підтримують log4net і NLog. Castle також підтримує System.Diagnostics. В обох випадках, коли платформа вирішує залежності для даного об'єкта (наприклад, коли платформа створює MyClass, а MyClass залежить від ILogger), вона делегує створення залежності (ILogger) "провайдеру" ILogger (резольвер може бути більш загальний термін). Потім реалізація постачальника ILogger відповідає за фактичне створення екземпляра екземпляра ILogger і передачу його назад, щоб потім вводити до залежного класу (наприклад, MyClass). В обох випадках постачальник / вирішувач знає тип залежного класу (наприклад, MyClass). Отже, коли MyClass створено і його залежності вирішуються, "вирішувач" ILogger знає, що клас - MyClass. У випадку використання рішень для ведення журналу Castle або NInject, це означає, що рішення реєстрації (реалізоване як обгортка через log4net або NLog) отримує тип (MyClass), тому воно може делегувати до log4net.LogManager.GetLogger () NLog.LogManager.GetLogger (). (Не на 100% впевнений у синтаксисі для log4net та NLog, але ви розумієте).

Хоча AutoFac і StructureMap не надають можливості ведення журналу (принаймні, що я міг би визначити, подивившись), вони, схоже, надають можливість реалізації користувацьких розв'язувачів. Отже, якщо ви хочете написати свій власний рівень абстракції журналів, ви можете також написати відповідний спеціальний вирішувач. Таким чином, коли контейнер хоче вирішити ILogger, ваш резольвер буде використаний для отримання правильного ILogger І він матиме доступ до поточного контексту (тобто які об'єктні залежності в даний час задовольняються - який об'єкт залежить від ILogger). Отримайте тип об’єкта, і ви готові делегувати створення ILogger поточно налаштованій платформі ведення журналу (яку ви, мабуть, абстрагували за інтерфейсом і для якої ви написали вирішувач).

Отже, кілька ключових моментів, які, як я підозрював, потрібні, але які я раніше не повністю зрозумів:

  1. Зрештою контейнер DI повинен якось усвідомлювати, яку платформу реєстрації використовувати. Зазвичай це робиться шляхом вказівки того, що "ILogger" повинен вирішуватися "вирішувачем", який є специфічним для платформи реєстрації (отже, Castle має log4net, NLog та ". Специфікацію того, який резольвер використовувати, можна зробити за допомогою конфігураційного файлу або програмно.

  2. Розпорядник повинен знати контекст, для якого вирішується залежність (ILogger). Тобто, якщо MyClass був створений і він залежить від ILogger, тоді, коли вирішувач намагається створити правильний ILogger, він (резольвер) повинен знати поточний тип (MyClass). Таким чином, вирішувач може використовувати базову реалізацію ведення журналу (log4net, NLog тощо), щоб отримати правильний реєстратор.

Ці моменти можуть бути очевидними для тих користувачів DI / IoC там, але я тільки зараз це вживаю, тому мені знадобився час, щоб обійти голову.

Одне, що я ще не з’ясував, - це те, як і чи можливо щось подібне за допомогою MEF. Чи можу я отримати об'єкт, який залежить від інтерфейсу, а потім виконати мій код після того, як MEF створив об'єкт і поки вирішується інтерфейс / залежність? Отже, припустимо, у мене є такий клас:

public class MyClass
{
  [Import(ILogger)]
  public ILogger logger;

  public MyClass()
  {
  }

  public void DoSomething()
  {
    logger.Info("Hello World!");
  }
}

Коли MEF вирішує питання імпорту для MyClass, чи можу я отримати власний код (через атрибут, через додатковий інтерфейс реалізації ILogger, в іншому місці ???), виконати та вирішити імпорт ILogger, виходячи з того, що він є MyClass, який наразі знаходиться в контексті, і повертає (потенційно) інший екземпляр ILogger, ніж буде отримано для YourClass? Чи застосовую я якийсь постачальник MEF?

На даний момент я все ще не знаю про MEF.


Схоже, MEF та Unity (IoC із групи MS Patterns & Practices) - це як порівняння яблук та апельсинів. Можливо, справжній контейнер IoC - це те, що потрібно для цієї конкретної проблеми, яка мала б точки розширюваності, щоб додати спеціальне вирішення залежностей. stackoverflow.com/questions/293051/…
Norman H

Ого ... деякі люди справді ускладнювали прості речі набагато складніше, ніж мали бути. Цікаво, наскільки все це насправді корисно, коли ви просто хочете написати якийсь текст у файл ...
Ед С.

5

Я бачу, ви зрозуміли власну відповідь :) Але для людей у ​​майбутньому, у яких виникає питання про те, як НЕ прив’язати себе до певної системи реєстрації, ця бібліотека: Common.Logging допомагає саме з цим сценарієм.


Той самий коментар, що і для qstarin. Дякуємо за вказівник на Common.Logging.
wageoghe

0

Я створив свій власний ServiceExportProvider , провайдером, я реєструю реєстратор Log4Net для введення залежностей за допомогою MEF. В результаті ви можете використовувати реєстратор для різних видів ін'єкцій.

Приклад ін'єкцій:

[Export]
public class Part
{
    [ImportingConstructor]
    public Part(ILog log)
    {
        Log = log;
    }

    public ILog Log { get; }
}

[Export(typeof(AnotherPart))]
public class AnotherPart
{
    [Import]
    public ILog Log { get; set; }
}

Приклад використання:

class Program
{
    static CompositionContainer CreateContainer()
    {
        var logFactoryProvider = new ServiceExportProvider<ILog>(LogManager.GetLogger);
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        return new CompositionContainer(catalog, logFactoryProvider);
    }

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        var container = CreateContainer();
        var part = container.GetExport<Part>().Value;
        part.Log.Info("Hello, world! - 1");
        var anotherPart = container.GetExport<AnotherPart>().Value;
        anotherPart.Log.Fatal("Hello, world! - 2");
    }
}

Результат у консолі:

2016-11-21 13:55:16,152 INFO  Log4Mef.Part - Hello, world! - 1
2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2

Реалізація ServiceExportProvider :

public class ServiceExportProvider<TContract> : ExportProvider
{
    private readonly Func<string, TContract> _factoryMethod;

    public ServiceExportProvider(Func<string, TContract> factoryMethod)
    {
        _factoryMethod = factoryMethod;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
    {
        var cb = definition as ContractBasedImportDefinition;
        if (cb?.RequiredTypeIdentity == typeof(TContract).FullName)
        {
            var ce = definition as ICompositionElement;
            var displayName = ce?.Origin?.DisplayName;
            yield return new Export(definition.ContractName, () => _factoryMethod(displayName));
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.