자습서: .NET에서 종속성 주입 사용
이 자습서에서는 .NET에서 DI(종속성 주입)를 사용하는 방법을 보여 줍니다. Microsoft 확장을 사용하면 DI는 서비스를 추가하고 IServiceCollection에서 구성하여 관리됩니다. IHost 인터페이스는 등록된 모든 서비스의 컨테이너 역할을 하는 IServiceProvider 인스턴스를 공개합니다.
이 자습서에서는 다음 작업 방법을 알아봅니다.
- 종속성 주입을 사용하는 .NET 콘솔 앱 만들기
- 제네릭 호스트 빌드 및 구성
- 여러 인터페이스 및 해당 구현 작성
- DI를 위해 서비스 수명 및 범위 지정 사용
사전 요구 사항
- .NET Core 3.1 SDK 이상
- 새 .NET 애플리케이션 만들기 및 NuGet 패키지 설치에 관한 지식
새 콘솔 애플리케이션 만들기
dotnet new 명령 또는 IDE 새 프로젝트 마법사를 사용하여 ConsoleDI.Example 라는 새 .NET 콘솔 애플리케이션을 만듭니다. 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.Hosting" Version="8.0.0" />
</ItemGroup>
</Project>
중요
이 예제에서는 앱을 빌드하고 실행하기 위해 Microsoft.Extensions.Hosting NuGet 패키지가 필요합니다. 일부 메타패키지에는 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
는 ServiceLifetime.Transient
값을 사용하여 IReportServiceLifetime.Lifetime
을 명시적으로 구현합니다.
기본 구현 추가
예제 구현은 모두 Guid.NewGuid() 결과를 사용하여 Id
속성을 초기화합니다. 다양한 서비스에 대한 다음 기본 구현 클래스를 프로젝트 루트 디렉터리에 추가합니다.
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
로 정의되고 해당 인터페이스를 구현합니다. 예를 들어 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 패키지를 작성하지 않는 한 Microsoft.Extensions.DependencyInjection 네임스페이스에 확장 메서드를 배치하지 마세요. Microsoft.Extensions.DependencyInjection
네임스페이스 내에 정의된 확장 메서드:
- 추가
using
블록 없이 IntelliSense에 표시됩니다. - 일반적으로 해당 확장 메서드가 호출되는
Program
또는Startup
클래스에서 필요한using
문의 수를 줄입니다.
앱은 다음을 수행합니다.
- 호스트 작성기 설정을 사용하여 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