Comments 31
А как же самый простой способ, работающий в Core "из коробки" — передача самих параметров конфигурации через DI при помощи 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;
}
Большинство реализаций DI с низким порогом входа либо предполагают что все сервисы — одиночки, либо предлагают использовать аналог паттерна Service Locator для создания тех сервисов, которые одиночками не являются. И сверху на это все — трудности с определением времени жизни объектов.
Но в PHP так делать можно потому что он создан чтобы умирать: среда выполнения приберет все объекты после окончания обработки запроса. А вот C# такой роскоши не предоставляет (зато дает возможность просто держать в памяти все то, ради чего программисты на PHP ставят отдельные key-value хранилища).
PHP не обязан умирать с версии эдак 5.3 (начиная от банальных воркеров и заканчивая ReactPHP)
Но обычно все же умирает. Применим ли фреймворк Symfony для неумирающей версии PHP?
Symfony Service Container позволяет как регистрировать синглтон, так и отдавать каждый раз новый инстанс
Нет, Service Locator использовать не нужно
Допустим, вы настроили Symfony Service Container так, чтобы он отдавал каждый раз новый инстанс. Как теперь его получить не используя ничего Service Locator-подобного?
Где получить? Заинжектить в другой сервис? Если да, то также как и синглтон. Для другого сервиса не важно как и откуда возьмется его зависимость. Это знает лишь контейнер
Как получить инстанс службы A изнутри службы B если время жизни службы A меньше чем время жизни службы B? Допустим, служба A создается на каждый запрос, а служба B висит в памяти в течении всего времени жизни сервера.
Вот, уже третья сущность появилась — фабрика… Надеюсь, они там автоматически создаются?
Контекст/сессию ORM обычно в таком режиме рекомендуют использовать
А вот это как раз и связано с тем, что PHP создан чтобы умирать. Как только вы перейдете на тот же ReactPHP — у вас появится проблема возможного накопления мусора в глобальном состоянии.
В случае же с ORM накопление мусора даже не считается проблемой, а является довольно важной фичей. Точнее, двумя фичами — кешированием сущностей и отслеживанием изменений.
PS извиняюсь, первый раз не увидел про воркеры.
В таком случае возможны две причины почему не возникало проблем с накоплением состояния:
- Вы не используете продвинутых ORM-фреймворков в воркерах;
- Вы создаете контекст ORM в обход IoC контейнера.
Можете поделиться своим опытом? Было бы интересно.
В вашем случае вижу это примерно так:
- пишется статический класс предоставляющий доступ к конфигурации, например так: https://github.com/s-tarasov/SerialsCalendar/blob/master/Calendar.Web/Configuration/ConfigurationProvider.cs
- до создания контейнера приложение инициализирует класс конфигурацией
- далее класс используется в модулях для получения конфигурации
особо сильных плюсов не вижу, но тем не менее:
- нет необходимости передавать в модули конфигурацию
- можно написать модули не зависимые от Microsoft.Framework.Configuration (если есть необходимость)
Но мне все же кажется, что это не самое уместное использование окружающего контекста. Все-таки он больше подходит для случаев, когда есть много потребителей, которым требуется получать какое-либо разделяемое состояние.
В случае же с конфигурированием модулей нам в общем случае требуется один раз при инициализации приложения получить данные из контекста. То есть потребители в данном случае только модули. И нужен им этот контекст только один раз. Да и данные в контексте уже определены.
При такой потребности выглядит проще передать (ну или заинъектить) конфигурацию один раз. Но тут на вкус и цвет. Ваш вариант не менее рабочий и не менее удобный!
P.S. Что-то вроде окружающего контекста мы используем допустим для разделения текущей транзакции между всеми командами и запросами, когда работаем с UnitOfWork. Только в данном случае этот контекст у нас ThreadStatic.
Передача параметров конфигураций в модули Autofac-а в ASP.NET Core