Следует признать, что с появлением платформы .NET Core и обновлением ASP.NET Core практика внедрения зависимостей в .NET ничуть не утратила актуальности. Лаконичный кейс об использовании встроенных контейнеров на платформе .NET разобран в статье Эндрю Лока, перевод которой мы сегодня предлагаем вашему вниманию
Максимум потенциала ASP.NET Core заключен во внедрении зависимостей (DI). Возможны различные разногласия о способах реализации DI, но, в целом, эта практика рекомендуется к использованию и мне кажется однозначно выигрышной.
Вам придется выбрать, какой контейнер использовать: встроенный или сторонний, однако, в конечном итоге вопрос сводится к тому, достаточно ли мощным получается создаваемый вами контейнер для конкретного проекта. В небольшом проекте все может быть нормально, но, если требуется регистрация на основе соглашений, инструменты для логирования/отладки, либо возникают более экзотические прикладные случаи, например, используется внедрение свойств — то проект придется обогащать. К счастью, сторонние контейнеры интегрируются довольно легко и тем временем упрощаются сами.
Зачем использовать встроенный контейнер?
Один из неоднократно встречавшихся мне вопросов – можно ли использовать встроенный провайдер в консольном приложении .NET Core? Коротко – нет, по крайней мере, не «из коробки», однако, добавить такой провайдер совершенно легко. Однако, уже другой вопрос – а стоит ли его использовать.
Одно из достоинств встроенного контейнера в ASP.NET Core заключается в том, что библиотеки фреймворка сами регистрируют с ним свои зависимости. При вызове расширяющего метода AddMvc()
в методе Startup.ConfigureServices
фреймворк зарегистрирует в контейнере целую кучу сервисов. Если позже добавить сторонний контейнер, то к нему перейдут и эти зависимости, и их придется перерегистрировать, чтобы они нормально разрешались через сторонний контейнер.
Если вы пишете консольное приложение, то, скорее всего, вам не понадобится MVC или другие ASP.NET-специфичные сервисы. В таком случае может быть столь же просто прямо с самого начала использовать StructureMap
или AutoFac
вместо встроенного провайдера, возможности которого ограничены.
При этом у самых распространенных сервисов, разработанных для использования с ASP.NET Core, будут расширения для регистрации со встроенным контейнером при помощи IServiceCollection
, поэтому, если вы используете такие сервисы как логирование или паттерн Options, то наверняка будет проще использовать готовые расширения, подключая поверх них сторонние решения, если таковые потребуются.
Добавляем инъекцию зависимостей в консольное приложение
Если вы решили, что встроенный контейнер вам подходит, то добавить его в приложение не составляет труда – для этого используется пакет Microsoft.Extensions.DependencyInjection
. Чтобы показать, как это делается, создам простое приложение с двумя сервисами:
public interface IFooService { void DoThing(int number); } public interface IBarService { void DoSomeRealWork(); }
У каждого из этих сервисов будет единственная реализация. BarService
зависит от IFooService
, а FooService
использует ILoggerFactory
для логирования некоторой работы:
public class BarService : IBarService { private readonly IFooService _fooService; public BarService(IFooService fooService) { _fooService = fooService; } public void DoSomeRealWork() { for (int i = 0; i < 10; i++) { _fooService.DoThing(i); } } } public class FooService : IFooService { private readonly ILogger<FooService> _logger; public FooService(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<FooService>(); } public void DoThing(int number) { _logger.LogInformation($"Doing the thing {number}"); } }
Как вы уже видите, я использую в приложении новую инфраструктуру логирования – поэтому добавлю соответствующий пакет в project.json
. Также добавлю пакет DependencyInjection
и пакет Microsoft.Extensions.Logging.Console
, чтобы можно было просматривать результаты логирования:
{ "dependencies": { "Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.DependencyInjection": "1.0.0" } }
Наконец, чтобы окончательно сложить все вместе, обновлю мой метод static void main
. Сейчас мы его подробно разберем.
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; public class Program { public static void Main(string[] args) { // настроим нашу инъекцию зависимостей var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton<IFooService, FooService>() .AddSingleton<IBarService, BarService>() .BuildServiceProvider(); // конфигурируем консольное логирование serviceProvider .GetService<ILoggerFactory>() .AddConsole(LogLevel.Debug); var logger = serviceProvider.GetService<ILoggerFactory>() .CreateLogger<Program>(); logger.LogDebug("Starting application"); // здесь выполняется работа var bar = serviceProvider.GetService<IBarService>(); bar.DoSomeRealWork(); logger.LogDebug("All done!"); } }
Первым делом мы сконфигурируем контейнер инъекции зависимостей, создав ServiceCollection
, добавив наши зависимости и, наконец, собрав IServiceProvider
. Этот процесс равнозначен методу ConfigureServices
в проекте ASP.NET Core, причем, здесь в фоновом режиме происходит практически то же самое. Как видите, мы используем расширяющий метод IServiceCollection
, чтобы добавить в наше приложение сервисы логирования, а затем регистрируем наши собственные сервисы. serviceProvider
– это наш контейнер, которым мы можем пользоваться для разрешения сервисов в нашем приложении.
На следующем этапе нам потребуется сконфигурировать инфраструктуру логирования с провайдером, так, чтобы результаты логирования куда-то выводились. Сначала выберем экземпляр ILoggerFactory
из нашего новоиспеченного serviceProvider
и добавим консольный логгер.
В оставшейся части программы мы видим дальнейшее внедрение зависимостей. Сначала выбираем ILogger<T>
из контейнера, а затем — экземпляр IBarService
. Согласно нашим регистрациям, IBarService
– это экземпляр BarService
, в который будет внедрен экземпляр FooService
.
Далее мы можем запустить наше приложение и убедиться, как красиво разрешаются все наши зависимости!
Добавление StructureMap к консольному приложению
Как было описано выше, встроенный контейнер удобен для добавления библиотек фреймворков при помощи методов расширений, как в уже разобранном примере с AddLogging
. Однако, он значительно уступает по возможностям многим сторонним контейнерам.
Для полноты картины покажу, как легко обновить приложение в расчете на гибридный подход: одновременно использовать встроенный контейнер для добавления любых зависимостей фреймворков, а для нашего собственного кода использовать StructureMap
. Более подробно о добавлении StructureMap
в приложение ASP.NET Core рассказано здесь.
Сначала нужно добавить StructureMap
к зависимостям project.json
:
{ "dependencies": { "StructureMap.Microsoft.DependencyInjection": "1.2.0" } }
Далее обновить метод static void main
, чтобы StructureMap
использовался для регистрации наших собственных зависимостей:
public static void Main(string[] args) { // добавляем сервисы фреймворка var services = new ServiceCollection() .AddLogging(); // добавляем StructureMap var container = new Container(); container.Configure(config => { // Регистрируем информацию в контейнере при помощи нужных API StructureMap… config.Scan(_ => { _.AssemblyContainingType(typeof(Program)); _.WithDefaultConventions(); }); // Заполняем контейнер информацией из коллекции сервисов config.Populate(services); }); var serviceProvider = container.GetInstance<IServiceProvider>(); // оставшаяся часть метода не изменилась }
На первый взгляд такая версия метода может показаться сложнее предыдущей – на самом деле, так и есть – но, в то же время, этот метод и гораздо мощнее. В примере со StructureMap
не придется явно регистрировать наши сервисы IFooService
или IBarService
– они автоматически регистрировались по соглашению. Когда приложение начинает разрастаться, подобная регистрация на основе соглашений становится чрезвычайно мощным инструментом, особенно в сочетании с отладочными возможностями и вариантами исправления ошибок, которые перед вами открываются.
В этом примере я показал, как использовать StructureMap
с адаптером для работы с методами расширения IServiceCollection
, однако, мой вариант, безусловно, не является обязательным. Совершенно допустимо применять StructureMap
как единственный источник регистрации, просто требуется вручную регистрировать все сервисы, добавляемые в составе методов расширения AddPLUGIN
.
Итоги
В этой статье я рассказал, как целесообразно использовать встроенный контейнер для внедрения зависимостей в приложении .NET Core. Я показал, как добавить к проекту коллекцию ServiceCollection
, зарегистрировать и сконфигурировать фреймворк логирования, а также извлечь из него сконфигурированные экземпляры сервисов. Наконец, я продемонстрировал, как можно использовать сторонний контейнер в комбинации со встроенным, чтобы усовершенствовать процедуру регистрации – в частности, реализовать регистрацию по соглашениям.
.
Весь проект (а также многие другие проекты, о которых я рассказывал у себя в блоге) вы найдете у меня на Github.
ссылка на оригинал статьи https://habr.com/post/421943/
Добавить комментарий