Pull to refresh

Comments 31

А как же самый простой способ, работающий в Core "из коробки" — передача самих параметров конфигурации через DI при помощи IOptions?

Безусловно, IOptions — это удобный способ передачи любых параметров внутри фреймворка. Например, если вы хотите передать какие-то параметры в контроллер.

Но если речь идет о других сервисах, то вам придется добавлять еще один конструктор, принимающий IOptions (ну или вообще сделать только один конструктор). Таким образом вы обяжете все ваши приложения (и это далеко не только ASP.NET Core) создавать сервисы, передавая в них IOptions. При этом в библиотеках с вашими сервисами еще и появится лишняя зависимость — Microsoft.Extensions.Options. В итоге, при работе с такими сервисами вы будете навязывать использование IOptions даже там, где это не нужно.

Ну и напоследок — для каждого сервиса, требующего параметров, вам придется регистрировать соответствующие TOptions в ConfigureServices, то есть опять-таки изменять этот метод и загромождать его регистрациями теперь уже не самих сервисов, а параметров для этих сервисов. В итоге бОльшую часть вашего ConfigureServices займет код:

services.Configure(Configuration);
services.Configure(Configuration);
services.Configure(Configuration);

Выпилилась часть кода:

services.Configure<SomeOptions1>(Configuration);
services.Configure<SomeOptions2>(Configuration);
services.Configure<SomeOptions3>(Configuration);

Это про ConfigureServices из предыдущего моего коммента.
Ну и напоследок — для каждого сервиса, требующего параметров, вам придется регистрировать соответствующие TOptions в ConfigureServices, то есть опять-таки изменять этот метод и загромождать его регистрациями теперь уже не самих сервисов, а параметров для этих сервисов.

Можно сократить количество кода, если использовать подход из статьи Скотта Аллена «Keeping a Clean Startup.cs in Asp.Net Core».

Спасибо за ссылку! Практикуем.
Есть ряд часто используемых extension-ов. Например для локализации дефолтных сообщений об ошибках при model binding-e (LocalizeModelBindingErrorMessages()) или для биндинга модели к конкретному классу-наследнику в зависимости от какого-либо переданного значения, когда в экшене указан класс-родитель (UserHierarchyTypeModelBinding()).

Правильнее было сказать, что проблема не в том, что ConfigureServices будет завален регистрациями IOptions-ов, а в том, что они в принципе будут — неважно где, в ConfigureServices или в каком-либо extension-е. И эти регистрации все равно придется добавлять по мере необходимости передавать параметры в новые сервисы.
При этом в библиотеках с вашими сервисами еще и появится лишняя зависимость — Microsoft.Extensions.Options.

Ваша мысль, безусловно, здравая, но, если хочется избежать зависимости от IOptions<T>, что помешает считать конфигурацию в ваш собственный класс и зарегистрировать в контейнере как синглтон? Заметьте, это решение будет работать с любым DI-контейнером, в отличие от вашего Autofac-specific кода.


бОльшую часть вашего ConfigureServices займет код

решается рефакторингом.


P.S. Борьба с "лишними" зависимостями иногда напоминает одержимость примитивными типами.

Рекомендую глянуть реализацию RegisterAssemblyModules в исходниках Autofac. Ваш код можно ужать в несколько раз если для загрузки модулей использовать дополнительный контейнер.

Добрался до компа. Вот тот самый более простой способ:


public static ContainerBuilder RegisterConfiguredModulesFromAssemblyContaining<TType>(
    this ContainerBuilder builder, 
    IConfigurationRoot configuration)
{
    var metabuilder = new ContainerBuilder();
    metabuilder.RegisterInstance(configuration);
    metabuilder.RegisterAssemblyTypes(typeof(TType).Assembly)
      .AssignableTo<IModule>().As<IModule>().PropertiesAutowired();

    foreach (var m in metabuilder.Build().Resolve<IEnumerable<IModule>>())
      builder.RegisterModule(m);
    return builder;
}
Спасибо за комментарии.

Решение с использованием доп. контейнера тоже рассматривали. По какой причине отмели — теперь уже не понимаю, если честно.
Действительно проще и не менее удобно.
UFO landed and left these words here

Большинство реализаций DI с низким порогом входа либо предполагают что все сервисы — одиночки, либо предлагают использовать аналог паттерна Service Locator для создания тех сервисов, которые одиночками не являются. И сверху на это все — трудности с определением времени жизни объектов.


Но в PHP так делать можно потому что он создан чтобы умирать: среда выполнения приберет все объекты после окончания обработки запроса. А вот C# такой роскоши не предоставляет (зато дает возможность просто держать в памяти все то, ради чего программисты на PHP ставят отдельные key-value хранилища).

UFO landed and left these words here
PHP не обязан умирать с версии эдак 5.3 (начиная от банальных воркеров и заканчивая ReactPHP)

Но обычно все же умирает. Применим ли фреймворк Symfony для неумирающей версии PHP?


Symfony Service Container позволяет как регистрировать синглтон, так и отдавать каждый раз новый инстанс
Нет, Service Locator использовать не нужно

Допустим, вы настроили Symfony Service Container так, чтобы он отдавал каждый раз новый инстанс. Как теперь его получить не используя ничего Service Locator-подобного?

UFO landed and left these words here
Где получить? Заинжектить в другой сервис? Если да, то также как и синглтон. Для другого сервиса не важно как и откуда возьмется его зависимость. Это знает лишь контейнер

Как получить инстанс службы A изнутри службы B если время жизни службы A меньше чем время жизни службы B? Допустим, служба A создается на каждый запрос, а служба B висит в памяти в течении всего времени жизни сервера.

UFO landed and left these words here

Вот, уже третья сущность появилась — фабрика… Надеюсь, они там автоматически создаются?

UFO landed and left these words here

Контекст/сессию ORM обычно в таком режиме рекомендуют использовать

UFO landed and left these words here

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


В случае же с ORM накопление мусора даже не считается проблемой, а является довольно важной фичей. Точнее, двумя фичами — кешированием сущностей и отслеживанием изменений.

PS извиняюсь, первый раз не увидел про воркеры.


В таком случае возможны две причины почему не возникало проблем с накоплением состояния:


  1. Вы не используете продвинутых ORM-фреймворков в воркерах;
  2. Вы создаете контекст ORM в обход IoC контейнера.
UFO landed and left these words here

Вы используете глобальный UoW — или все же создаете его на запрос? :-)

UFO landed and left these words here

Общий UoW на все запросы?.. Мда, ничего не понимаю.

UFO landed and left these words here
Рассматривали вариант использовать конфигурацию приложения как Ambient Context зависимость?
Нет, к сожалению, не рассматривали. Навскидку не понятны плюсы от такого подхода.

Можете поделиться своим опытом? Было бы интересно.

В вашем случае вижу это примерно так:



особо сильных плюсов не вижу, но тем не менее:


  • нет необходимости передавать в модули конфигурацию
  • можно написать модули не зависимые от Microsoft.Framework.Configuration (если есть необходимость)
Спасибо за пример!

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

P.S. Что-то вроде окружающего контекста мы используем допустим для разделения текущей транзакции между всеми командами и запросами, когда работаем с UnitOfWork. Только в данном случае этот контекст у нас ThreadStatic.
Only those users with full accounts are able to leave comments. Log in, please.