.NET의 종속성 주입 기본 사항 이해
이 문서에서는 ServiceCollection 및 해당하는 ServiceProvider를 수동으로 만드는 .NET 콘솔 앱을 만듭니다. DI(종속성 주입)를 사용하여 서비스를 등록하고 해결하는 방법을 알아봅니다. 이 문서에서는 Microsoft.Extensions.DependencyInjection NuGet 패키지를 사용하여 .NET에서 DI의 기본 사항을 보여 줍니다.
참고 항목
이 문서에서는 일반 호스트 기능을 활용하지 않습니다. 좀 더 포괄적인 가이드는 .NET에서의 종속성 주입 사용을 참조하세요.
시작하기
시작하려면 DI.Basics라는 새 .NET 콘솔 애플리케이션을 만듭니다. 콘솔 프로젝트를 만드는 가장 일반적인 방법 중 일부는 다음 목록에서 참조할 수 있습니다.
- Visual Studio: 파일 > 새로 만들기 > 프로젝트 메뉴.
- Visual Studio Code 및 C# 개발 키트 확장: 솔루션 탐색기 메뉴 옵션.
- .NET CLI: 터미널의
dotnet new console
명령.
프로젝트 파일에서 Microsoft.Extensions.DependencyInjection에 패키지 참조를 추가해야 합니다. 접근 방식에 관계없이 프로젝트가 DI.Basics.csproj 파일의 다음 XML과 유사한지 확인합니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
</Project>
종속성 주입 기본 사항
종속성 주입은 하드 코드된 종속성을 제거하고 애플리케이션의 유지 관리 및 테스트 효율을 높일 수 있게 해 주는 디자인 패턴입니다. DI는 클래스와 해당 종속성 간에 IoC(Inversion of Control)를 달성하기 위한 기술입니다.
.NET에서 DI에 대한 추상화는 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 패키지에 정의됩니다.
- IServiceCollection: 서비스 설명자 컬렉션에 대한 계약을 정의합니다.
- IServiceProvider: 서비스 개체를 검색하는 메커니즘을 정의합니다.
- ServiceDescriptor: 서비스 종류, 구현 및 수명을 포함하여 서비스에 대해 설명합니다.
.NET에서 DI는 IServiceCollection
에서 서비스를 추가하고 구성하는 방식으로 관리됩니다. 서비스가 등록된 후 IServiceProvider
인스턴스는 BuildServiceProvider 메서드를 호출하여 빌드됩니다. IServiceProvider
는 등록된 모든 서비스의 컨테이너 역할을 하며 서비스를 확인하는 데 사용됩니다.
example 서비스 만들기
모든 서비스가 동일하게 만들어지는 것은 아닙니다. 어떤 서비스는 서비스 컨테이너가 인스턴스를 가져올 때마다 새 인스턴스가 필요하지만(transient), 요청 간에(scoped) 또는 앱의 전체 수명 동안(singleton) 공유해야 하는 서비스도 있습니다. 서비스 수명에 대한 자세한 내용은 서비스 수명을 참조하세요.
마찬가지로 일부 서비스는 구체적인 형식만 노출하고 또 일부 서비스는 인터페이스와 구현 형식 간의 계약으로 표현됩니다. 이러한 개념을 설명하는 데 도움이 되도록 여러 가지 서비스 변형을 만듭니다.
IConsole.cs라는 새 C# 파일을 만들고 다음 코드를 추가합니다.
public interface IConsole
{
void WriteLine(string message);
}
이 파일은 단일 메서드 WriteLine
을 노출하는 IConsole
인터페이스를 정의합니다. 그런 다음 DefaultConsole.cs라는 새 C# 파일을 만들고 다음 코드를 추가합니다.
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
앞의 코드는 IConsole
인터페이스의 기본 구현을 나타냅니다. WriteLine
메서드는 IsEnabled
속성에 따라 조건부로 콘솔에 씁니다.
팁
구현의 명명은 개발 팀이 동의해야 하는 선택 사항입니다. Default
접두사는 인터페이스의 기본 구현을 나타내는 일반적인 규칙이지만 꼭 필요하지는 않습니다.
다음으로, IGreetingService.cs 파일을 만들고 다음 C# 코드를 추가합니다.
public interface IGreetingService
{
string Greet(string name);
}
그런 다음 DefaultGreetingService.cs라는 새 C# 파일을 추가하고 다음 코드를 추가합니다.
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
앞의 코드는 IGreetingService
인터페이스의 기본 구현을 나타냅니다. 서비스 구현에는 기본 생성자 매개 변수로 IConsole
이 필요합니다. Greet
메서드는 다음 작업을 수행합니다.
name
이 지정되면greeting
을 만듭니다.IConsole
인스턴스에서WriteLine
메서드를 호출합니다.greeting
을 호출자에게 반환합니다.
마지막으로 만들 서비스는 FarewellService.cs 파일이며 계속하기 전에 다음 C# 코드를 추가합니다.
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
FarewellService
는 인터페이스가 아닌 구체적인 형식을 나타냅니다. 소비자가 액세스할 수 있도록 public
으로 선언해야 합니다. internal
및 sealed
로 선언된 다른 서비스 구현 형식과 달리 이 코드는 모든 서비스가 인터페이스일 필요는 없음을 보여 줍니다. 또한 상속을 방지하기 위한 sealed
및 어셈블리에 대한 액세스를 제한하기 위한 internal
일 수 있음을 보여 줍니다.
Program
클래스를 업데이트합니다.
Program.cs 파일을 열고 기존 코드를 다음 C# 코드로 바꿉니다.
using Microsoft.Extensions.DependencyInjection;
// 1. Create the service collection.
var services = new ServiceCollection();
// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
});
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();
// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();
// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();
// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");
위의 업데이트된 코드에서는 다음 방법을 보여 줍니다.
- 새
ServiceCollection
인스턴스를 만듭니다. ServiceCollection
에서 서비스를 등록 및 구성합니다.- 구현 팩터리 오버로드를 사용하는
IConsole
은IsEnabled
가 'true'로 설정된DefaultConsole
형식을 반환합니다. IGreetingService
는 해당 구현 형식의DefaultGreetingService
형식으로 추가됩니다.FarewellService
는 구체적인 형식으로 추가됩니다.
- 구현 팩터리 오버로드를 사용하는
ServiceCollection
에서ServiceProvider
를 빌드합니다.IGreetingService
및FarewellService
서비스를 확인합니다.- 확인된 서비스를 사용하여
David
라는 사람에게 인사하고 작별 인사를 합니다.
DefaultConsole
의 IsEnabled
속성을 false
로 업데이트하는 경우 Greet
및 SayGoodbye
메서드는 결과 메시지에 콘솔에 쓰는 것을 생략합니다. 이와 같이 변경하면 IConsole
서비스가 IGreetingService
및 FarewellService
에 해당 앱 동작에 영향을 주는 종속성으로 주입된다는 것으로 보여 주는 데 도움이 됩니다.
이러한 서비스는 모두 싱글톤으로 등록되어 있지만, 이 샘플에서는 transient 또는 scoped 서비스로 등록되어 있어도 동일하게 작동합니다.
Important
이 모순된 example에서는 서비스 수명이 중요하지 않지만 실제 애플리케이션에서는 각 서비스의 수명을 신중하게 고려해야 합니다.
샘플 앱 실행
샘플 앱을 실행하려면 Visual Studio 또는 Visual Studio Code에서 F5를 누르거나 터미널에서 dotnet run
명령을 실행합니다. 앱이 완료되면 다음 출력이 표시됩니다.
Hello, David!
Goodbye, David!
서비스 설명자
서비스를 ServiceCollection
에 추가하는 데 가장 일반적으로 사용되는 API는 다음과 같은 수명 이름이 지정된 제네릭 확장 메서드입니다.
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
이러한 메서드는 ServiceDescriptor 인스턴스를 만들어 ServiceCollection
에 추가하는 편리한 메서드입니다. ServiceDescriptor
는 서비스 종류, 구현 및 수명을 포함하여 서비스에 대해 설명하는 단순한 클래스입니다. 구현 팩터리 및 인스턴스를 설명할 수도 있습니다.
ServiceCollection
에 등록한 각 서비스에 대해 ServiceDescriptor
인스턴스를 사용하여 Add
메서드를 대신 직접 호출할 수 있습니다. 다음 예를 살펴 보십시오.
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
앞의 코드는 IConsole
서비스가 ServiceCollection
에 등록된 방식과 동일합니다. Add
메서드는 IConsole
서비스를 설명하는 ServiceDescriptor
인스턴스를 추가하는 데 사용됩니다. 정적 메서드ServiceDescriptor.Describe
는 다양한 ServiceDescriptor
생성자에 위임합니다. IGreetingService
서비스에 해당하는 코드를 고려합니다.
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
앞의 코드는 서비스 종류, 구현 및 수명을 포함하여 IGreetingService
서비스에 대해 설명합니다. 마지막으로, FarewellService
서비스에 해당하는 코드를 고려합니다.
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
앞의 코드는 구체적인 FarewellService
형식을 서비스 및 구현 형식으로 설명합니다. 서비스는 singleton 서비스로 등록되어 있습니다.
참고 항목
.NET