Здравствуйте, коллеги.
Следует признать, что с появлением платформы .NET Core и обновлением ASP.NET Core практика внедрения зависимостей в .NET ничуть не утратила актуальности. Лаконичный кейс об использовании встроенных контейнеров на платформе .NET разобран в статье Эндрю Лока, перевод которой мы сегодня предлагаем вашему вниманию
Максимум потенциала ASP.NET Core заключен во внедрении зависимостей (DI). Возможны различные разногласия о способах реализации DI, но, в целом, эта практика рекомендуется к использованию и мне кажется однозначно выигрышной.
Вам придется выбрать, какой контейнер использовать: встроенный или сторонний, однако, в конечном итоге вопрос сводится к тому, достаточно ли мощным получается создаваемый вами контейнер для конкретного проекта. В небольшом проекте все может быть нормально, но, если требуется регистрация на основе соглашений, инструменты для логирования/отладки, либо возникают более экзотические прикладные случаи, например, используется внедрение свойств — то проект придется обогащать. К счастью, сторонние контейнеры интегрируются довольно легко и тем временем упрощаются сами.
Зачем использовать встроенный контейнер?
Один из неоднократно встречавшихся мне вопросов – можно ли использовать встроенный провайдер в консольном приложении .NET Core? Коротко – нет, по крайней мере, не «из коробки», однако, добавить такой провайдер совершенно легко. Однако, уже другой вопрос – а стоит ли его использовать.
Одно из достоинств встроенного контейнера в ASP.NET Core заключается в том, что библиотеки фреймворка сами регистрируют с ним свои зависимости. При вызове расширяющего метода
Если вы пишете консольное приложение, то, скорее всего, вам не понадобится MVC или другие ASP.NET-специфичные сервисы. В таком случае может быть столь же просто прямо с самого начала использовать
При этом у самых распространенных сервисов, разработанных для использования с ASP.NET Core, будут расширения для регистрации со встроенным контейнером при помощи
Добавляем инъекцию зависимостей в консольное приложение
Если вы решили, что встроенный контейнер вам подходит, то добавить его в приложение не составляет труда – для этого используется пакет
У каждого из этих сервисов будет единственная реализация.
Как вы уже видите, я использую в приложении новую инфраструктуру логирования – поэтому добавлю соответствующий пакет в
Наконец, чтобы окончательно сложить все вместе, обновлю мой метод
Первым делом мы сконфигурируем контейнер инъекции зависимостей, создав
На следующем этапе нам потребуется сконфигурировать инфраструктуру логирования с провайдером, так, чтобы результаты логирования куда-то выводились. Сначала выберем экземпляр
В оставшейся части программы мы видим дальнейшее внедрение зависимостей. Сначала выбираем
Далее мы можем запустить наше приложение и убедиться, как красиво разрешаются все наши зависимости!
Добавление StructureMap к консольному приложению
Как было описано выше, встроенный контейнер удобен для добавления библиотек фреймворков при помощи методов расширений, как в уже разобранном примере с
Для полноты картины покажу, как легко обновить приложение в расчете на гибридный подход: одновременно использовать встроенный контейнер для добавления любых зависимостей фреймворков, а для нашего собственного кода использовать
Сначала нужно добавить
Далее обновить метод
На первый взгляд такая версия метода может показаться сложнее предыдущей – на самом деле, так и есть – но, в то же время, этот метод и гораздо мощнее. В примере со
В этом примере я показал, как использовать
Итоги
В этой статье я рассказал, как целесообразно использовать встроенный контейнер для внедрения зависимостей в приложении .NET Core. Я показал, как добавить к проекту коллекцию
Весь проект (а также многие другие проекты, о которых я рассказывал у себя в блоге) вы найдете у меня на Github.
Следует признать, что с появлением платформы .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.
retran
Статья 2016 года. С project.json, ага…