자습서: .NET에서 종속성 주입 사용
이 자습서에서는 .NETDI(
이 자습서에서는 다음 방법을 알아봅니다.
- 종속성 주입을 사용하는 .NET 콘솔 앱 만들기
- 제네릭 호스트 빌드 및 구성
- 여러 인터페이스 및 해당 구현 작성
- DI에 대한 서비스 수명 및 범위 지정 사용
필수 구성 요소
- .NET Core 3.1 SDK 이상.
- 새 .NET 애플리케이션을 만들고 NuGet 패키지를 설치하는 방법에 대해 잘 알고 있습니다.
새 콘솔 애플리케이션 만들기
dotnet new 명령 또는 IDE 새 프로젝트 마법사를 사용하여 ConsoleDI라는 새 .NET 콘솔 애플리케이션을 만듭니다.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.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
</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
정의되고 해당 인터페이스를 구현합니다.
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 패키지를 작성하지 않는 한 확장 메서드를 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 종속성 주입 기본 사항 이해
- ASP.NET Core에서의 종속성 주입
.NET