Руководство. Использование внедрения зависимостей в .NET
В этом руководстве показано, как использовать внедрение зависимостей (DI) в .NET. С помощью расширений Майкрософт, DI управляется путем добавления служб и их настройки в IServiceCollection. Интерфейс IHost предоставляет экземпляр IServiceProvider, который выступает в качестве контейнера всех зарегистрированных служб.
В этом руководстве описано, как:
- Создание консольного приложения .NET, использующего внедрение зависимостей
- Создание и настройка универсального узла
- Создание нескольких интерфейсов и соответствующих реализаций
- Используйте время жизни службы и область видимости для внедрения зависимостей
Необходимые условия
- пакет SDK для .NET Core 3.1 или более поздней версии.
- Знакомство с созданием новых приложений .NET и установкой пакетов NuGet.
Создание консольного приложения
С помощью команды dotnet new или мастера создания нового проекта в IDE создайте консольное приложение .NET с именем ConsoleDI.Example. Добавьте в проект пакет Microsoft.Extensions.Hosting NuGet.
Новый файл проекта консольного приложения должен выглядеть следующим образом:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RootNamespace>ConsoleDI.Example</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
</ItemGroup>
</Project>
Важный
В этом примере для сборки и запуска приложения требуется пакет Microsoft.Extensions.Hosting
, в этом случае не требуется явная ссылка на пакет.
Добавление интерфейсов
В этом образце приложения вы узнаете, как внедрение зависимости управляет жизненным циклом службы. Вы создадите несколько интерфейсов, представляющих разные сроки существования службы. Добавьте следующие интерфейсы в корневой каталог проекта:
IReportServiceLifetime.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
Интерфейс IReportServiceLifetime
определяет следующее:
- Свойство
Guid Id
, представляющее уникальный идентификатор службы. - Свойство ServiceLifetime, представляющее время существования службы.
IExampleTransientService.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleTransientService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}
IExampleScopedService.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleScopedService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}
IExampleSingletonService.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleSingletonService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
Все подинтерфейсы IReportServiceLifetime
явно реализуют IReportServiceLifetime.Lifetime
по умолчанию. Например, IExampleTransientService
явным образом реализует IReportServiceLifetime.Lifetime
посредством значения ServiceLifetime.Transient
.
Добавление реализаций по умолчанию
Все примеры реализаций инициализируют своё свойство Id
с результатом Guid.NewGuid(). Добавьте следующие классы реализации по умолчанию для различных служб в корневой каталог проекта:
ExampleTransientService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleTransientService : IExampleTransientService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleScopedService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleScopedService : IExampleScopedService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleSingletonService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleSingletonService : IExampleSingletonService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
Каждая реализация определяется как internal sealed
и реализует соответствующий интерфейс. Они не обязательно должны быть internal
или sealed
, однако обычно используются для обработки реализаций как internal
, чтобы избежать утечки типов реализаций внешним потребителям. Кроме того, поскольку каждый тип не будет расширен, он помечен как sealed
. Например, ExampleSingletonService
реализует IExampleSingletonService
.
Добавить службу, требующую DI
Добавьте следующий класс аналитика продолжительности службы, который действует как служба в консольном приложении.
ServiceLifetimeReporter.cs
namespace ConsoleDI.Example;
internal sealed class ServiceLifetimeReporter(
IExampleTransientService transientService,
IExampleScopedService scopedService,
IExampleSingletonService singletonService)
{
public void ReportServiceLifetimeDetails(string lifetimeDetails)
{
Console.WriteLine(lifetimeDetails);
LogService(transientService, "Always different");
LogService(scopedService, "Changes only with lifetime");
LogService(singletonService, "Always the same");
}
private static void LogService<T>(T service, string message)
where T : IReportServiceLifetime =>
Console.WriteLine(
$" {typeof(T).Name}: {service.Id} ({message})");
}
ServiceLifetimeReporter
определяет конструктор, который требует каждого из указанных выше интерфейсов службы, то есть IExampleTransientService
, IExampleScopedService
и IExampleSingletonService
. Объект предоставляет один метод, позволяющий потребителю составлять отчет о службе с указанным параметром lifetimeDetails
. При вызове метод ReportServiceLifetimeDetails
регистрирует уникальный идентификатор каждой службы с сообщением о времени существования службы. Сообщения журнала помогают визуализировать время существования службы.
Регистрация служб для DI
Обновите Program.cs следующим кодом:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();
using IHost host = builder.Build();
ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");
await host.RunAsync();
static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine();
}
Каждый метод расширения services.Add{LIFETIME}<{SERVICE}>
добавляет (и потенциально настраивает) службы. Мы рекомендуем приложениям следовать этому соглашению. Не размещайте методы расширения в пространстве имен Microsoft.Extensions.DependencyInjection, если вы не создаете официальный пакет Майкрософт. Методы расширения, определенные в пространстве имен Microsoft.Extensions.DependencyInjection
:
- Отображаются в IntelliSense без дополнительных директив
using
. - Уменьшите количество обязательных директив
using
в классахProgram
илиStartup
, где обычно вызываются эти методы расширения.
Приложение:
- Создает экземпляр IHostBuilder с параметрами построителя хоста .
- Настраивает службы и добавляет их с соответствующим временем существования службы.
- Вызывает Build() и присваивает экземпляр IHost.
- Вызывает
ExemplifyScoping
, передавая IHost.Services.
Заключение
В этом примере приложения вы создали несколько интерфейсов и соответствующих реализаций. Каждая из этих служб однозначно идентифицируется и связана с ServiceLifetime. В примере приложения демонстрируется регистрация реализаций служб в интерфейсе и регистрация чистых классов без поддержки интерфейсов. Затем в примере приложения показано, как зависимости, определенные как параметры конструктора, разрешаются во время выполнения.
При запуске приложения отображаются выходные данные, аналогичные следующим:
// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
//
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
В выходных данных приложения вы увидите следующее:
- Transient сервисы всегда отличаются, создается новый экземпляр с каждым запросом сервиса.
- Scoped службы изменяются только с новой областью, но являются тем же экземпляром в пределах области.
- Singleton сервисы всегда одинаковы, новый экземпляр создается лишь один раз.
См. также
- рекомендации по внедрению зависимостей
- Основные сведения о внедрении зависимостей в .NET
- Внедрение зависимостей в ASP.NET Core