다음을 통해 공유


.NET의 종속성 주입 기본 사항 이해

이 문서에서는 ServiceCollection 및 해당하는 ServiceProvider를 수동으로 만드는 .NET 콘솔 앱을 만듭니다. DI(종속성 주입)를 사용하여 서비스를 등록하고 해결하는 방법을 알아봅니다. 이 문서에서는 Microsoft.Extensions.DependencyInjection NuGet 패키지를 사용하여 .NET에서 DI의 기본 사항을 보여 줍니다.

참고 항목

이 문서에서는 일반 호스트 기능을 활용하지 않습니다. 좀 더 포괄적인 가이드는 .NET에서의 종속성 주입 사용을 참조하세요.

시작하기

시작하려면 DI.Basics라는 새 .NET 콘솔 애플리케이션을 만듭니다. 콘솔 프로젝트를 만드는 가장 일반적인 방법 중 일부는 다음 목록에서 참조할 수 있습니다.

프로젝트 파일에서 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으로 선언해야 합니다. internalsealed로 선언된 다른 서비스 구현 형식과 달리 이 코드는 모든 서비스가 인터페이스일 필요는 없음을 보여 줍니다. 또한 상속을 방지하기 위한 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에서 서비스를 등록 및 구성합니다.
    • 구현 팩터리 오버로드를 사용하는 IConsoleIsEnabled가 'true'로 설정된 DefaultConsole 형식을 반환합니다.
    • IGreetingService는 해당 구현 형식의 DefaultGreetingService 형식으로 추가됩니다.
    • FarewellService는 구체적인 형식으로 추가됩니다.
  • ServiceCollection에서 ServiceProvider를 빌드합니다.
  • IGreetingServiceFarewellService 서비스를 확인합니다.
  • 확인된 서비스를 사용하여 David라는 사람에게 인사하고 작별 인사를 합니다.

DefaultConsoleIsEnabled 속성을 false로 업데이트하는 경우 GreetSayGoodbye 메서드는 결과 메시지에 콘솔에 쓰는 것을 생략합니다. 이와 같이 변경하면 IConsole 서비스가 IGreetingServiceFarewellService에 해당 앱 동작에 영향을 주는 종속성으로 주입된다는 것으로 보여 주는 데 도움이 됩니다.

이러한 서비스는 모두 싱글톤으로 등록되어 있지만, 이 샘플에서는 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 서비스로 등록되어 있습니다.

참고 항목