Pull to refresh

Comments 37

Для Microsoft DI это давно решается маленькими самописными фабриками, которые настраиваются как душе угодно - хоть передачей типа запрашивающего, хоть передачей enum-ключа для выбора нужной реализации.

Возможно я не в курсе, приведите пример. Потому что мне кажется, что такого не может быть. Ибо для автовайринга нужно менять сам контейнер, который резолвит цепочку и по всей цепочке он должен прокидывать признак предъявителя. И собственно то, что Вы назвали фабрикой кажется и я назвал провайдером. И в нём сейчас нет возможности представиться. И в колбеке тоже нет. Контекст, который передаётся в колбек это просто контейнер с сервисами, откуда можно брать предустановленное ранее. Но это делается без какого-либо динамического критерия. Возможно по конфигу да, но не по какому-то признаку предъявителя, от чего нельзя как я написал в примере иметь разные имплементации для разных мест. Но я вполне допускаю, что я просто не в курсе. Покажите пример как это делается, пожалуйста.

см. AddTransient<TService, TImplementation>(Func<IServiceProvider,TImplementation> implementationFactory)

Вы значит просто не поняли, в чём суть проблемы и предложения. Попробуйте применить это к описанному примеру и убедитесь, что у Вас нет способа определить в этой фабрике, кому какую имплементацию отдать.

Из того что есть в наличии, наверно альтернативой могло бы быть собственно инъекция фабрики, с резолвингом зависимости уже внутри конструктора или даже в рантайме

class Consumer
{
  IDependency _dependency;
  
  public Consumer(IDependencyFactory factory)
  {
    _dependency = factory.Create(this);
  }
}

Но опять же, в описанном случае если уже есть 100500 мест где эта зависимость резолвится напрямую - те же проблемы с переписываением.

Да, всё верно, в этом и проблема

Моё предложение в том, чтобы предоставлять фабрики не для зависимостей (логгеры и т.п.), а для самих контроллеров. Попробуйте в коллекции сервисов регистрировать сами контроллеры со кастомной фабрикой, а ASP.NET фреймворк должен их ресолвить наравне с другими зависимостями, и таким образом, оно пройдёт через вашу фабрику


services.AddScoped<MyController1>(s => new MyController1(fileLogger));
services.AddScoped<MyController2>(s => new MyController2(nullLogger));

Это лучшая пока идея в текущих условиях

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

Например так:

void Main()
{
    var services = new ServiceCollection();
    services.AddTransient<DefaultFoo>();
    services.AddTransient<Foo1>();
    services.AddTransient<Foo2>();
    services.AddSingleton<FooFactory>();
    services.AddTransient<Client1>();
    services.AddTransient<Client2>();
    var provider = services.BuildServiceProvider();
    
    var client1 = provider.GetRequiredService<Client1>();
    var client2 = provider.GetRequiredService<Client2>();
    IFoo defaultFoo = provider.GetRequiredService<FooFactory>().GetFoo(null);
    defaultFoo.Bar();
}

public interface IFoo
{
    void Bar();
}

public class DefaultFoo : IFoo
{
    public void Bar() => Console.WriteLine(nameof(DefaultFoo));
}

public class Foo1 : IFoo
{
    public void Bar() => Console.WriteLine(nameof(Foo1));
}

public class Foo2 : IFoo
{
    public void Bar() => Console.WriteLine(nameof(Foo2));
}

public class FooFactory
{
    private readonly IServiceProvider _sp;
    public FooFactory(IServiceProvider sp) => _sp = sp;
    public IFoo GetFoo(object client)
    {
        return client switch
        {
            Client1 => _sp.GetRequiredService<Foo1>(),
            Client2 => _sp.GetRequiredService<Foo2>(),
            _ => _sp.GetRequiredService<DefaultFoo>()
        };
    }
}

public class Client1
{
    public Client1(FooFactory fooFactory)
    {
        Console.WriteLine(nameof(Client1));
        IFoo foo = fooFactory.GetFoo(this);
        foo.Bar();
    }
}

public class Client2
{
    public Client2(FooFactory fooFactory)
    {
        Console.WriteLine(nameof(Client2));
        IFoo foo = fooFactory.GetFoo(this);
        foo.Bar();
    }
}
Client1
Foo1
Client2
Foo2
DefaultFoo

Вариант, который не подходит. По условиям задачи, код классов Client1 и Client2 модифицировать нельзя.

Нет такого условия в задаче (во всяком случае, на момент написания комментария).

Если не хочется менять классы-клиенты, то можно реализовать собственный IServiceProvider.GetService(Type serviceType), который всё это делает, и подменить им нативный:

вот так
void Main()
{
    var builder = Host.CreateDefaultBuilder();
    builder.UseServiceProviderFactory(new MyServiceProviderFactory());
    builder.ConfigureServices(services =>
    {
        services.AddTransient<IFoo, DefaultFoo>();
        services.AddTransient<Foo1>();
        services.AddTransient<Foo2>();
        services.AddTransient<Client1>();
        services.AddTransient<Client2>();
    });

    var provider = builder.Build().Services;

    var client1 = provider.GetRequiredService<Client1>();
    var client2 = provider.GetRequiredService<Client2>();
    IFoo defaultFoo = provider.GetRequiredService<IFoo>();
    defaultFoo.Bar();
}

public class MyServiceProvider : IServiceProvider
{
    private readonly ServiceProvider _nativeServiceProvider;
    public MyServiceProvider(ServiceProvider sp)
    {
        _nativeServiceProvider = sp;
    }

    public object GetService(Type serviceType)
    {
        return serviceType switch
        {
            Type _ when serviceType == typeof(Client1) =>
                ActivatorUtilities.CreateInstance(_nativeServiceProvider, serviceType, _nativeServiceProvider.GetRequiredService<Foo1>()),
            Type _ when serviceType == typeof(Client2) =>
                ActivatorUtilities.CreateInstance(_nativeServiceProvider, serviceType, _nativeServiceProvider.GetRequiredService<Foo2>()),
            _ => _nativeServiceProvider.GetRequiredService(serviceType)
        };
    }
}

public class MyContainerBuilder
{
    public IServiceCollection Services { get; set; }
    public IServiceProvider ServiceProvider => new MyServiceProvider(Services.BuildServiceProvider());
}

public class MyServiceProviderFactory : IServiceProviderFactory<MyContainerBuilder>
{
    public MyContainerBuilder CreateBuilder(IServiceCollection services)
    {
        return new MyContainerBuilder { Services = services };
    }

    public IServiceProvider CreateServiceProvider(MyContainerBuilder containerBuilder)
    {
        return containerBuilder.ServiceProvider;
    }
}

public interface IFoo
{
    void Bar();
}

public class DefaultFoo : IFoo
{
    public void Bar() => Console.WriteLine(nameof(DefaultFoo));
}

public class Foo1 : IFoo
{
    public void Bar() => Console.WriteLine(nameof(Foo1));
}

public class Foo2 : IFoo
{
    public void Bar() => Console.WriteLine(nameof(Foo2));
}

public class Client1
{
    public Client1(IFoo foo)
    {
        Console.WriteLine(nameof(Client1));
        foo.Bar();
    }
}

public class Client2
{
    public Client2(IFoo foo)
    {
        Console.WriteLine(nameof(Client2));
        foo.Bar();
    }
}

Client1
Foo1
Client2
Foo2
DefaultFoo

Вы не понимаете проблему. `IServiceProvider.GetService(Type serviceType)` это запрос имплементации по указанному типу его контракта. Вы указываете, что хотите получить и получаете объект этого. Ваш код отдаёт разные клиенты потому что запрашиваются разные клиенты, он и написан в расчёте на это. А как Вы будете в контроллерах запрашивать разные? Вы пойдёте их все перепишите и укажите в каком какой отдавать. Хотя казалось бы, все кругом говорят о инверсии зависимостей, дескать зависеть надо от абстракций и в потребителях Ваших должны быть указаны интерфейсы, а как они резолвятся и что приходит в итоге это не важно, если оно соблюдает контракт. Но Вы не можете поставить абстракцию без дополнительного огорода получения её конкретной имплементации.

Я никого не хочу обидеть, но на мой взгляд в этом виноваты прежде всего Майкрософт, потому что они мало того, что за столько лет не могли ничего придумать, но по сути своим добавлением именованных сервисов легализовали костыли и сделали их нормой жизни. А теперь все спорят и доказывают, что это нормально. Это не нормально. Потому что мне проще тогда поставить в контроллерах явное указание конкретного логера забив на принцип инверсии зависимостей, ибо DI контейнер не предоставляет мне такую возможность. Собственно все как-то и живут с этим, городят огороды и всё это работает. Но это не значит, что это нормально. Во всяком случае я не собирался переубеждать тех, кому это нравится. Я предложил дополнение к самой концепции контейнера внедрения зависимостей, которая просто исключает всё это пространство лайфхаков.

  1. Не "я не понимаю проблему", а вы её не можете правильно сформулировать. Я прошу MyServiceProvider создать мне класс Client1, как-то самостоятельно выбрать для него реализацию IFoo и подставить её в конструктор клиенту, с чем он прекрасно справляется. Так же точно, в конструктор контроллера подставится ровно то, что я укажу в GetService. А настройки для GetService я могу прочитать хоть из .txt, хоть из БД, хоть из ChatGPT.

  2. Вы хотите странного, и хотите этого от Microsoft. Они уже сделали достаточно абстракций и точек расширения: не устраивает родной DI - используйте ninject/autofac/lightinject/etc или пишите свой.

Если бы Вы не отклонялись от предложенного мною примера, то наверное бы всё поняли.

Никакой контейнер не реализует эту возможность. Предложение реализовать свой конечно всегда выглядит солидно, только для него не нужна компетенция, его достаточно заучить и приходить в любую дискуссию дежурно вбрасывая предложение изобрести свой интернет. Ценность такого предложения равна нулю. Можно было просто пройти мимо моей заметки, если Вам она показалась глупой.

Может я не до конца понял проблему автора, но вот например что Castle.Windsor умеет:

    public class Installer : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                // register loggers, FileLogger is default
                Component.For<ILogger>().ImplementedBy<FileLogger>().IsDefault(),
                Component.For<ILogger>().ImplementedBy<RabbitMqLogger>(),
                Component.For<ILogger>().ImplementedBy<PostgreSqlLogger>(),

                // register controllers
                // first 3 controllers uses default logger
                Component.For<Controller1>(),
                Component.For<Controller2>(),
                Component.For<Controller3>(),

                // specify logger implementation for some controllers
                Component.For<Controller4>().DependsOn(Dependency.OnComponent<ILogger, RabbitMqLogger>()),
                Component.For<Controller5>().DependsOn(Dependency.OnComponent<ILogger, PostgreSqlLogger>())
            );
        }
    }

Все контроллеры имеют зависимость типа ILogger. 4 и 5 контроллеру в момент регистрации говорим какой логгер будет для них резолвится.

Я не адепт Castle.Windsor, но пользуюсь им уже достаточно давно и могу сказать, что в плане кастомизации он очень хорош.

Это вы не пытаетесь вникнуть в решения, которые вам предлагают.


ASP.NET сервер при получении http-запроса будет "под капотом" дёргать
provider.GetRequiredService<Controller1>();
provider.GetRequiredService<Controller2>();
и конфигурировать эти самые Controller1 и Controller2 теми зависимостями, которые предоставляет фабрика. Всё, контроллеры сами ничего не знают о своих логгерах, вся настройка происходит в composition root.


Я предложил дополнение к самой концепции контейнера внедрения зависимостей, которая просто исключает всё это пространство лайфхаков.

Ваше предложение не вписывается в концепцию "чистого кода", когда приложение с инверсией зависимостей может быть написано просто прямой подстановкой зависимостей в конструкторы, без всяких контейнеров. А что если компонент создаёт другой компонент просто через new, передавая в конструктор часть своих зависимостей? Это нормальная практика, чтобы не добавлять зависимость от сервис-провайдера.

Я не просил решения, я знаю, что их существует много. Я описал и пример, и проблему, и решение. А множество рецептов Вам никто не запрещает использовать.

Добавлю ещё пунктик, почему так делать не надо (как вы предложили, и как benjik выше предложил).


Допустим, ваш контроллер зависит от сервисов IUserService и IOrdersService. Контейнер при создании самого MyController подменит логгер, но не подменит его во вложенных зависимостях (вы же явно не указывали, что в UserService надо кастомный логгер), и в результате корневой метод будет логгировать куда надо, а все его зависимые методы куда не надо.


Поэтому правильно делать фабрику, но не так, как benjik написал выше, а чуть хитрее: для каждой группы контроллеров иметь свою коллекцию сервисов, с разной регистрацией ILogger, и соответствующий контроллер доставать из соответствующей коллекции, чтобы ILooger подменился капитально, во всех зависимостях тоже.

Разве Keyd Services в .NET 8 не решают эту проблему?

Смотря что считать проблемой. Я написал, что не решают. Но мы все как-то живём и что-то пишем, как-то выкручиваемся. Именованные сервисы это разочарование. Майкрософт сделало нормальным очередную плохую практику. Проще указать в зависимостях тогда конкретный файловый логер, например, чем городить какой-то ключ, который указывает то же самое.

Идея в том, чтобы дополнить принцип действия контейнера таким механизмом, чтобы всегда передавать предъявителя запроса и таким образом можно забыть про костыли. Проблема не только сишарповская. Я точно знаю, что есть такая же в php на Laravel, потому что там контейнер работает так же.

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

Что-то мне кажется, что не прокатит такая задумка. Проблема в том, что нельзя однозначно сказать, кто будет тем самым "предъявителем". К примеру, у вас есть контроллер, который запрашивает ISomeService и IControllerDependService, при этом ISomeService так же запрашивает IControllerDependService. Как вы будете резолвить нужный вашему контроллеру IControllerDependService, если первым его запросит ISomeService?

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

Я предложил новый интерфейс, хотя не уверен в оптимальности предложения. Но даже при таком варианте в общем случае для разработчика ничего не поменяется, как были зарегистрированы зависимости так и остались. Но теперь можно конкретному контракту положить не его имплементацию, а провайдера. А в самом контейнере нужно добавить условие: если запрошенная зависимость это провайдер, то делегировать резолвинг ему. И собственно всё.

А провайдере должно быть всё необходимое: сам контейнер с сервисами в нём, но ещё и пришедший в аргументе критерий, по которому можно сделать вариативность резолвинга.

Это точечный механизм не затрагивающий общую ситуацию. Но только в том случае, если в самом контейнере это условие с передачей типа предъявителя есть. То есть нельзя это реализовать без переделки самого контейнера.

А предъявитель всегда известен, ибо в любой момент времени где бы то ни было кто-то же вызывает метод получения какого-то сервиса из контейнера — вот пусть передаёт не только то, что хочет получить, но и дополнительно сообщает, кто он такой.

Таких сценариев в общем случае два:

  • автоматический, типа как с контроллерами

  • и ручной, когда мы сами пишем какую-то логику с запросом каких-то зависимостей.

Можно сделать критерий налабл типа и тогда его можно не передавать потому что по дефолту будет null, а провайдер тогда отдаст дефолтную имплементацию.

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

Я не могу предложить пул-реквест в язык, потому что недостаточно просто что-то написать, чтобы работало, там же множество правил, которые я не знаю, планы релизного цикла, какие-то конвенции явные и неявные. У Майкрософт плохая привычка изобретать кучу спецификаций на ровном месте и поэтому недостаточно знать сишарп, чтобы это написать. Вы заглядывали в исходники? Там же через эмит рефлексию на их высокоуровневом ассемблере дописано для производительности. Под капотом используется куча технологий и правила использования каждой знать надо. Это не просто.

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

К слову, некоторые IOC имеют такую фичу:

Context-based injection is the ability to inject a particular dependency based on the context it lives in (or change the implementation based on the type it is injected into). Simple Injector contains the RegisterConditional method overloads that enable context-based injection.

Advanced Scenarios — Simple Injector 5 documentation

Кажется это условие прописывается по статичным критериям, например значения конфига. Это можно сделать и сейчас, определив делегат, в котором написать сколь угодно сложную логику вариативности, только она не привязана к потребителю зависимости, она будет отдавать всем контроллерам нечто одно.

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

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

Насколько я понял, есть три сервиса и три разных поведения в каждом. Почему нельзя вынести способ логгирования в env и регистрировать конкретный логгер в каждом их трёх сервисов?

Можно, почему нет. Только при такой постановке вопроса можно вообще что-то написать без контейнера, или переделать архитектуру разделив сервисы на разные приложения. Но это просто другая тема.

Пример я придумал чтобы продемонстрировать проблему именно контейнера зависимостей. Его можно развивать дальше. Заказчик может менять требования каждый день. Это вполне правдоподобно, так как это может не от него зависеть, ибо распределённые большие системы отданные на аутсорс могут порождать подобные требования через посредников внезапно и часто, и времени на их реализацию выделять мало. И понять заказчиков можно, они же не обязаны знать несовершенство чьей-то архитектуры, для них это простой вопрос трёх заказанных ими сервисов и разделения трафика логов по ним. А завтра заказчик ещё придумает нечто подобное. Каждый раз архитектуру менять?

Так что это не вопрос переменных окружения. Это вопрос отсутствия возможностей управления полиморфизмом в контейнере зависимостей.

Подсунуть вместо ISomeLogger что-то свое с вот таким внутри:

var methodInfo = new StackTrace().GetFrame(1).GetMethod();
var className = methodInfo.ReflectedType.Name;

if (className in ...)
...

Не то, чтобы я за подобное, но в рамках ваших ограничений работает же.

Но будем честны — это не решает проблему. Проще сразу поставить в зависимости конкретные типы.

Почему не решает? Потому что нужно будет один раз отредактировать нужные контроллеры?

Поставив конкретные типы вы обрекаете себя на необходимость изменять эти типы, если для какого-то сервиса вдруг понадобится подменить провайдер логирования, например для тестов. В случае именованной регистрации сервисов - достаточно будет подменить реализацию только в Startup.cs.

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

Почему? Под именем "file" может скрываться создание файла с описанием ошибки и отправки его на почту разработчику, а может скрываться реализация записи данных в стандартный журнал ОС или что-то еще.

Да, вы зависите от конкретного именованного сервиса, но при этом не знаете какая реализация скрыта за этим именем, т.е. вы все еще зависите от абстракции (чуть более конкретной, но все еще абстракции), что и является определением инверсии зависимостей.

SOLID - это рекомендации по разработке ПО, а не конкретные требования. Иначе можем дойти до того, что нужны разные DTO и контроллеры для каждого действия CRUD над моделью данных из-за принципа единственной ответственности.

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

передав что именно запрашивается и кто именно запрашивает.

Ну т.е. конкретному поставщику зависимостей нужно будет знать о существовании всех контроллеров и на основе наименования/содержания/типа определять какой именно провайдер логирования нужен. В итоге, мы получим необходимость изменять +- тот же объем кода, просто в одном конкретном файле, а не в разных, при его добавлении в систему. В дальнейшем нам будет недостаточно сконфигурировать сервис в конструкторе, т.к. необходимо будет его добавить в поставщика зависимостей. А самый главный вопрос: какая именно реализация будет подставлена в контроллер, если мы забудем добавить его в список поставщика зависимостей? Как-то сложно, нет?

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

предоставить поставщику критерий, в соответствии с которым тот может резолвить зависимости для него.

Так вы и так предоставляете критерий при именованных зависимостях - имя.

Тэг на сервисе не должен говорить, как он это делает (file/rabbit/postgres), а должен говорить, какой бизнес-сценарий работает.


Например, если Some1Controller является сервисом для ООО "Ромашка", он должен быть объявлен как


builder.Services.AddSingleton<ILogger, FileLogger>("romashka");
...
public Some1Controller([FromKeyedServices("romashka")] ILogger logger) { ... }

И при конфигурировании контроллера мы лишь объявляем его предназначение, а связь предназначений с логгерами — в точке конфигурации. Может, ООО "Ромашка" завтра захочет логи в rabbit, и тогда не надо перерывать все контроллеры, а заменить нужно будет 1 строчку.


Как вариант, сервисы помечать тегами самого контроллера:


public Some1Controller([FromKeyedServices(nameof(Some1Controller))] ILogger logger) { ... }

builder.Services.AddSingleton<ILogger, FileLogger>(nameof(Some1Controller));

тут больший простор в конфигурации, контроллеры тоже не придётся переписывать при изменении правил логирования, и каждый контроллер можно конфигурировать независимо. За что придётся заплатить — больше строк в точке конфигурирования.

Тэг на сервисе не должен говорить, как он это делает (file/rabbit/postgres), а должен говорить, какой бизнес-сценарий работает.

Согласен. В таком случае мы реализуем любую логику для конкретной ответственности (контроллера) независимо от других ответственностей. Так же это позволяет использовать внутри конкретных методов дополнительные зависимости для тех же логов.

Это отличное решение, на самом деле. Я пытался это объяснить, но, возможно, некорректно это сделал.

Конкретно в случае логгирования, можно использовать условный Serilog, и дальше по получателям распределять уже с помощью его фильтров.

Рецептов много, далеко не все они хороши. Код от просто текста конечно отличает именно то, что он работает. Но мы заботимся об архитектуре потому что этого недостаточно. Нужно писать такой код, который был бы максимально производителен, удобен в развитии и не допускал бы по каждой правке переписывания половины проекта. Именно поэтому мы делим всё на хорошие и плохие практики.

По ссылке на ишу в гитхабе можно почитать обсуждение. Там разработчик Майкрософт предложил вместо провайдера вариант экстеншна, который делает то, что надо. Я ему предложил добавить свой экстеншн в нюгет пакет контейнера, поскольку это относится к апи контейнера. Но он не ответил больше. Я сомневаюсь, что это сделают. Хотя ишу не закрыли пока, она чего-то ждёт.

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

Предложено максимальное плохое решение. Логи писать можно куда угодно, как угодно, фильтровать, раскидывать по источникам в зависимости от имени логгера, да хоть рандомно, это задача самого логгера. NLog и Serilog так умеют, а если чего не умеют, так это легчайше расширяется. Пытаться решать это через DI всё равно, что затягивать болты на колёсах трактора микроскопом. Я если честно, вообще не понял, как можно было в решении задачи в принципе прийти к DI?

В первую очередь, не нужно никогда инжектить ILogger. Правильно инжектить ILogger<T>, где T это класс, где мы используем логгер.

public class MySomeController
{
   public MySomeController(..., ILogger<MySomeController> logger) { ... }
}

Теперь можно писать в logger, и у него будет известное нам имя логгера. Далее, настраиваем вывод каких угодно логгеров, хоть точечно, хоть по неймспейсам, куда угодно. Можно даже в разных форматах. Хотим, писать логи контроллера в один файл, а логи другого контроллера в эластик? Без проблем! Хотим дебаг-логи писать в консоль, ошибки писать в файл, а ворнинги от конкретного логгера в БД? Без проблем! Это всё настраивается в конфиге логгера.

Не нужно изобретать крайне кривые косые велосипеды, которые мало того, что не позволяют ничего толком настроить, ещё и хардкод. Это же полностью противоречит всему, что сделало человечество за многие годы!

Вы не удосужились вчитаться, не поняли проблему. А вот разработчик из Майкрософт её понял и предложил вариант решения. Пойдите почитайте ишу и комментарии к ней на Гитхабе.

Так этот ответ решает бизнес-проблему, а не тупо следует вашему заранее выбранному решению (чтобы было решено "как я хочу", а не как большинство разработчиков посчитает архитектурно верным).

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

Я закрыл ишу на Гитхабе. К сожалению удалить свой пост на Хабре не могу. Предлагаю взять себя в руки и проигнорировать его. Мало ли какие дурачки добрались до Хабра и пишут что попало, не обращайте внимания.

Sign up to leave a comment.

Articles