ASP.NET Core 의존성 주입 기법 이해하기

의존성 주입의 기본 개념

ASP.NET Core는 의존성 주입(Dependency Injection, DI)을 기본적으로 지원하며, 이는 제어의 역전(Inversion of Control, IoC)을 구현하는 핵심 설계 패턴입니다.

DI 관련 추상화는 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 패키지에 포함되어 있으며, 주요 구성 요소는 다음과 같습니다:

  • IServiceCollection: 서비스 등록을 위한 인터페이스로, 서비스 디스크립터들의 집합을 관리합니다.
  • IServiceProvider: 등록된 서비스를 조회하고 반환하는 메커니즘을 정의합니다.
  • ServiceDescriptor: 서비스의 타입, 구현체, 생명주기 정보를 담고 있는 객체입니다.

DI를 사용하려면 먼저 IServiceCollection에 서비스를 추가하고 설정합니다. 이후 BuildServiceProvider()를 호출하여 IServiceProvider 인스턴스를 생성합니다. 이 인스턴스는 모든 등록된 서비스의 컨테이너 역할을 하며, 필요한 곳에서 서비스를 주입받을 수 있습니다.

콘솔 애플리케이션에서의 DI 활용

1. 프로젝트 설정

우선 .NET 콘솔 애플리케이션을 생성하고, Microsoft.Extensions.DependencyInjection 패키지를 설치합니다.

2. 인터페이스 및 구현 클래스 정의

public interface IOutput
{
    void WriteLine(string text);
}

internal sealed class ConsoleOutput : IOutput
{
    public bool Active { get; set; } = true;

    void IOutput.WriteLine(string text)
    {
        if (!Active) return;
        Console.WriteLine(text);
    }
}
public interface IGreeting
{
    string GenerateGreeting(string userName);
}

internal sealed class BasicGreeting(IOutput output) : IGreeting
{
    public string GenerateGreeting(string userName)
    {
        var message = $"안녕하세요, {userName}님!";
        output.WriteLine(message);
        return message;
    }
}

일반 클래스로도 서비스를 등록할 수 있습니다:

public class Farewell(IOutput output)
{
    public string SayGoodbye(string userName)
    {
        var message = $"안녕히 가세요, {userName}님!";
        output.WriteLine(message);
        return message;
    }
}

3. 서비스 등록 및 사용

using Microsoft.Extensions.DependencyInjection;

// 1. 서비스 컬렉션 생성
var services = new ServiceCollection();

// 2. 서비스 등록
services.AddSingleton<IOutput>(
    factory: _ => new ConsoleOutput { Active = true });
services.AddSingleton<IGreeting, BasicGreeting>();
services.AddSingleton<Farewell>();

// 3. 서비스 프로바이더 생성
var provider = services.BuildServiceProvider();

// 4. 필요한 서비스 조회
var greetingService = provider.GetRequiredService<IGreeting>();
var farewellService = provider.GetRequiredService<Farewell>();

// 5. 서비스 사용
var greeting = greetingService.GenerateGreeting("홍길동");
var farewell = farewellService.SayGoodbye("홍길동");

DI의 주요 장점

  • 인터페이스나 추상 클래스를 통해 의존성을 추상화합니다.
  • 의존성을 서비스 컨테이너에 등록합니다. .NET은 내장된 IServiceProvider 컨테이너를 제공하며, 서비스는 일반적으로 애플리케이션 시작 시 등록됩니다.
  • 의존성을 필요로 하는 클래스의 생성자에 자동으로 주입합니다. 프레임워크가 인스턴스 생성과 해제를 관리합니다. 단, builder.Services.AddSingleton(new Service1())처럼 직접 생성한 인스턴스는 프레임워크가 해제하지 않으므로 개발자가 직접 관리해야 합니다.

실행 결과:

안녕하세요, 홍길동님!
안녕히 가세요, 홍길동님!

ConsoleOutputActive 속성을 false로 변경하면 메시지가 출력되지 않습니다. 이를 통해 IOutput 서비스가 IGreetingFarewell 클래스에 주입되어 동작을 제어한다는 것을 확인할 수 있습니다.

웹 애플리케이션에서 DI 활용

인터페이스 및 구현 클래스

public interface ILogService
{
    void Log(string message);
}

public class ConsoleLogService : ILogService
{
    public void Log(string message)
    {
        Console.WriteLine($"[Log] {message}");
    }
}

애플리케이션 시작 시 서비스 해소

var builder = WebApplication.CreateBuilder(args);
// AddScoped는 요청당 한 번 생성되는 범위(Scoped) 생명주기로 등록
builder.Services.AddScoped<ILogService, ConsoleLogService>();

var app = builder.Build();

// 애플리케이션 시작 시 일시적으로 서비스 해소
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var logger = services.GetRequiredService<ILogService>();
    logger.Log("애플리케이션이 시작되었습니다.");
}

app.MapGet("/", () => "Hello World!");
app.Run();

생성자 주입 방식

public class IndexModel : PageModel
{
    private readonly ILogService _logger;

    public IndexModel(ILogService logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        _logger.Log("IndexPage.OnGet 호출됨");
    }
}

서비스 생명주기(Service Lifetime)

Transient (일시적)

AddTransient로 등록된 서비스는 요청할 때마다 새로운 인스턴스가 생성됩니다. 가벼운 서비스나 상태를 유지하지 않는 서비스에 적합합니다.

Scoped (범위)

AddScoped로 등록된 서비스는 웹 요청당 한 번 생성됩니다. 웹 애플리케이션에서는 각 HTTP 요청이 하나의 범위로 간주됩니다. 명시적으로 IServiceScopeFactory.CreateScope()를 사용해 범위를 만들 수도 있습니다.

범위 서비스는 반드시 범위 내에서 사용해야 합니다. ASP.NET Core의 요청 파이프라인은 암시적으로 범위를 제공합니다.

Singleton (단일)

AddSingleton으로 등록된 서비스는 애플리케이션 전체에서 하나의 인스턴스만 존재합니다. 처음 요청될 때 생성되거나, 개발자가 직접 인스턴스를 제공할 수도 있습니다(드문 경우).

단일 서비스는 스레드 안전성을 보장해야 하며, 일반적으로 무상태(stateless) 서비스에 사용됩니다.

태그: ASP.NET Core 의존성 주입 IOC IServiceCollection IServiceProvider

7월 3일 21:00에 게시됨