.NET 종속성 주입

.NET는 클래스와 해당 종속성 간의 IoC(Inversion of Control)를 실현하는 기술인 DI(종속성 주입) 소프트웨어 디자인 패턴을 지원합니다. .NET의 종속성 주입은 구성, 로깅 및 옵션 패턴과 함께 프레임워크에 기본 제공된 부분입니다.

‘종속성’은 다른 개체가 종속된 개체입니다. 다른 클래스가 종속된 MessageWriter 클래스에서 Write 메서드를 사용하는 다음 코드를 살펴보세요.

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

클래스에서 MessageWriter 클래스의 인스턴스를 만들어 Write 메서드를 사용할 수 있습니다. 다음 예제에서 MessageWriter 클래스는 Worker 클래스의 종속성입니다.

public class Worker : BackgroundService
{
    private readonly MessageWriter _messageWriter = new();

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

이 클래스는 MessageWriter 클래스를 만들고 이 클래스에 직접 종속됩니다. 이전 예제와 같은 하드 코딩된 종속성은 문제가 있으며 다음과 같은 이유로 사용하지 않아야 합니다.

  • MessageWriter를 다른 구현으로 바꾸려면 Worker 클래스를 수정해야 합니다.
  • MessageWriter에 종속성이 있으면 Worker 클래스에서 해당 종속성도 구성해야 합니다. 여러 클래스가 MessageWriter에 종속되어 있는 대형 프로젝트에서는 구성 코드가 앱 전체에 분산됩니다.
  • 이 구현은 단위 테스트하기가 어렵습니다. 앱에서 모의 또는 스텁 MessageWriter 클래스를 사용해야 하지만, 이 방법에서는 가능하지 않습니다.

종속성 주입은 다음을 통해 이러한 문제를 해결합니다.

  • 인퍼테이스 또는 기본 클래스를 사용하여 종속성 구현을 추상화합니다.
  • 서비스 컨테이너에 종속성 등록. .NET는 서비스 컨테이너인 IServiceProvider를 기본 제공합니다. 서비스는 일반적으로 앱 시작 시 등록되고 IServiceCollection에 추가됩니다. 모든 서비스가 추가되면 BuildServiceProvider를 사용하여 서비스 컨테이너를 만듭니다.
  • 서비스가 사용되는 클래스의 생성자에 주입됨. 프레임워크가 종속성의 인스턴스를 만들고 더 이상 필요하지 않으면 삭제하는 작업을 담당합니다.

예를 들어 IMessageWriter 인터페이스는 Write 메서드를 정의합니다.

namespace DependencyInjection.Example;

public interface IMessageWriter
{
    void Write(string message);
}

이 인터페이스는 구체적인 형식 MessageWriter에서 구현됩니다.

namespace DependencyInjection.Example;

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

샘플 코드에서는 IMessageWriter 서비스를 구체적인 형식 MessageWriter로 등록합니다. AddSingleton 메서드는 앱의 수명인 싱글톤 수명으로 서비스를 등록합니다. 서비스 수명에 대해서는 이 문서의 뒷부분에서 설명합니다.

using DependencyInjection.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

이전 코드에서 샘플 앱은 다음을 수행합니다.

  • 호스트 앱 작성기 인스턴스를 만듭니다.

  • 다음을 등록하여 서비스를 구성합니다.

    • 호스트된 서비스인 Worker입니다. 자세한 내용은 .NET의 작업자 서비스를 참조하세요.
    • MessageWriter 클래스의 해당 구현을 포함하는 싱글톤 서비스로서의 IMessageWriter 인터페이스.
  • 호스트를 빌드하고 실행합니다.

호스트에는 종속성 주입 서비스 공급자가 포함되어 있습니다. 또한 Worker를 자동으로 인스턴스화하고 해당 IMessageWriter 구현을 인수로 제공하는 데 필요한 기타 모든 관련 서비스도 포함되어 있습니다.

namespace DependencyInjection.Example;

public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

DI 패턴을 사용하여 작업자 서비스는

  • 구체적인 형식 MessageWriter를 사용하지 않고 구현되는 IMessageWriter 인터페이스만 사용합니다. 따라서 작업자 서비스를 수정하지 않고도 작업자 서비스가 사용하는 구현을 쉽게 변경할 수 있습니다.
  • MessageWriter의 인스턴스를 만들지 않습니다. 인스턴스는 DI 컨테이너에 의해 만들어집니다.

IMessageWriter 인터페이스의 구현을 기본 제공 로깅 API를 사용하여 향상할 수 있습니다.

namespace DependencyInjection.Example;

public class LoggingMessageWriter(
    ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
    public void Write(string message) =>
        logger.LogInformation("Info: {Msg}", message);
}

업데이트된 AddSingleton 메서드는 새 IMessageWriter 구현을 등록합니다.

builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

HostApplicationBuilder(builder) 형식은 Microsoft.Extensions.Hosting NuGet 패키지의 일부입니다.

LoggingMessageWriter는 생성자에서 요청하는 ILogger<TCategoryName>에 종속됩니다. ILogger<TCategoryName>프레임워크에서 제공하는 서비스입니다.

종속성 주입을 연결된 방식으로 사용하는 일은 특별한 경우가 아닙니다. 요청된 각 종속성은 차례로 자체 종속성을 요청합니다. 컨테이너는 그래프의 종속성을 해결하고 완전히 해결된 서비스를 반환합니다. 해결해야 하는 종속성이 모인 집합은 일반적으로 종속성 트리, 종속성 그래프 또는 개체 그래프라고 합니다.

컨테이너는 개방형 형식(제네릭)을 활용하여 ILogger<TCategoryName>을 해결하므로 모든 생성된 형식(제네릭)을 등록하지 않아도 됩니다.

종속성 주입 용어를 사용하는 경우 서비스는 다음과 같습니다.

  • 일반적으로 다른 개체에 IMessageWriter 서비스와 같은 서비스를 제공하는 개체입니다.
  • 서비스에서 웹 서비스를 사용할 수 있지만 웹 서비스와 관련 있는 것은 아닙니다.

해당 프레임워크는 강력한 로깅 시스템을 제공합니다. 이전 예제에 표시된 IMessageWriter 구현은 로깅을 구현하는 것이 아니라 기본 DI를 보여 주기 위해 작성되었습니다. 대부분의 앱에서는 로거를 작성할 필요가 없습니다. 다음 코드에서는 Worker를 호스팅된 서비스 AddHostedService로 등록해야 하는 기본 로깅을 사용하는 방법을 보여 줍니다.

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger) =>
        _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

위의 코드를 사용하면 프레임워크에서 로깅을 제공하므로 Program.cs를 업데이트할 필요가 없습니다.

다중 생성자 검색 규칙

형식에서 둘 이상의 생성자를 정의하는 경우 서비스 공급자는 사용할 생성자를 결정하는 논리를 포함합니다. 형식의 DI 확인이 가능한 대부분의 매개 변수가 있는 생성자가 선택됩니다. 다음 C# 서비스 예를 살펴보세요.

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService)
    {
        // omitted for brevity
    }
}

위 코드에서는 로깅이 추가되었고 서비스 공급자에서 확인할 수 있지만 FooServiceBarService 형식은 그렇지 않은 상황을 가정해 봅니다. ILogger<ExampleService> 매개 변수가 있는 생성자는 ExampleService 인스턴스를 확인하는 데 사용됩니다. 더 많은 매개 변수를 정의하는 생성자가 있는 경우에도 FooServiceBarService 형식은 DI 확인이 가능하지 않습니다.

생성자를 발견할 때 모호성이 있으면 예외가 throw됩니다. 다음 C# 서비스 예를 살펴보세요.

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Warning

모호한 DI 확인 가능 형식 매개 변수가 있는 ExampleService 코드에서는 예외가 발생합니다. 이 작업을 수행하지 마세요. 이는 "모호한 DI 확인 가능 형식"의 의미를 보여주고자 하는 것입니다.

위 예제에는 세 가지 생성자가 있습니다. 첫 번째 생성자는 매개 변수가 없으며 서비스 공급자의 서비스가 필요하지 않습니다. 로깅 및 옵션이 모두 DI 컨테이너에 추가되었고 DI 확인 가능 서비스라고 가정합니다. DI 컨테이너가 ExampleService 형식을 확인하려고 하면 두 생성자가 모호하기 때문에 예외가 발생합니다.

대신 DI 확인 가능 형식을 모두 허용하는 생성자를 정의하여 모호성을 피할 수 있습니다.

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(
        ILogger<ExampleService> logger,
        IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

확장 메서드를 사용하여 서비스 그룹 등록

Microsoft Extensions에서는 관련 서비스 그룹을 등록하는 규칙을 사용합니다. 규칙은 단일 Add{GROUP_NAME} 확장 메서드를 사용하여 프레임워크 기능에 필요한 모든 서비스를 등록하는 것입니다. 예를 들어 AddOptions 확장 메서드는 옵션을 사용하는 데 필요한 모든 서비스를 등록합니다.

프레임워크에서 제공하는 서비스

사용 가능한 호스트 또는 앱 작성기 패턴을 사용하는 경우 기본값이 적용되고 서비스는 프레임워크에 의해 등록됩니다. 가장 자주 사용되는 호스트 및 앱 작성기 패턴을 고려해보세요.

이러한 API에서 작성기를 만든 후 IServiceCollection에는 호스트 구성 방법에 따라 프레임워크에서 정의된 서비스가 있습니다. .NET 템플릿 기반 앱의 경우 프레임워크는 수백 개의 서비스를 등록할 수 있습니다.

다음 표에는 프레임워크에서 등록한 서비스들의 작은 샘플이 나열되어 있습니다.

서비스 유형 수명
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory 싱글톤
IHostApplicationLifetime 싱글톤
Microsoft.Extensions.Logging.ILogger<TCategoryName> 싱글톤
Microsoft.Extensions.Logging.ILoggerFactory 싱글톤
Microsoft.Extensions.ObjectPool.ObjectPoolProvider 싱글톤
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transient
Microsoft.Extensions.Options.IOptions<TOptions> 싱글톤
System.Diagnostics.DiagnosticListener 싱글톤
System.Diagnostics.DiagnosticSource 싱글톤

서비스 수명

다음 수명 중 하나를 사용하여 서비스를 등록할 수 있습니다.

다음 섹션에서는 위의 각 수명에 대해 설명합니다. 등록된 각 서비스의 수명을 적절히 선택합니다.

Transient

Transient 수명 서비스는 서비스 컨테이너에서 요청할 때마다 만들어집니다. 서비스를 임시등록하려면 .를 호출합니다AddTransient.

요청을 처리하는 앱에서 Transient 서비스는 요청이 끝날 때 삭제됩니다. 이 수명은 서비스가 매번 확인되고 생성되기 때문에 요청당 할당이 발생합니다. 자세한 내용은 종속성 주입 지침: 일시적 및 공유 인스턴스에 대한 IDisposable 지침을 참조하세요.

Scoped

웹 애플리케이션의 경우 범위가 지정된 수명은 클라이언트 요청(연결)마다 한 번씩 서비스가 생성됨을 나타냅니다. AddScoped를 사용하여 범위 지정된 서비스를 등록합니다.

요청을 처리하는 앱에서 Scoped 서비스는 요청이 끝날 때 삭제됩니다.

Entity Framework Core를 사용하는 경우 AddDbContext 확장 메서드는 기본적으로 범위가 지정된 수명으로 DbContext 형식을 등록합니다.

참고 항목

singleton에서 범위가 지정된 서비스를 해결하지 마세요. 예를 들어 임시 서비스를 통해 간접적으로 해결하지 않도록 주의해야 합니다. 이 경우 후속 요청을 처리할 때 서비스가 잘못된 상태일 수 있습니다. 다음 작업은 괜찮습니다.

  • 범위가 지정된 서비스 또는 임시 서비스에서 싱클톤 서비스를 해결합니다.
  • 다른 범위가 지정된 서비스 또는 임시 서비스에서 범위가 지정된 서비스를 해결합니다.

기본적으로 개발 환경에서 수명이 더 긴 다른 서비스에서 서비스를 해결하면 예외가 throw됩니다. 자세한 내용은 범위 유효성 검사를 참조하세요.

싱글톤

싱클톤 수명 서비스는 다음과 같은 경우 생성됩니다.

  • 처음 요청되는 경우
  • 개발자가 구현 인스턴스를 컨테이너에 직접 제공하는 경우 (이 방법은 거의 필요하지 않습니다.)

종속성 주입 컨테이너로부터 서비스 구현에 대한 모든 후속 요청은 동일한 인스턴스를 사용합니다. 앱에 싱글톤 동작이 필요한 경우 서비스 컨테이너가 서비스 수명을 관리하도록 허용합니다. 싱글톤 디자인 패턴을 구현하지 말고 싱글톤을 삭제하는 코드를 제공합니다. 컨테이너에서 서비스를 해결한 코드에서는 서비스를 삭제하면 안 됩니다. 형식 또는 팩터리가 싱글톤으로 등록된 경우 컨테이너에서 싱글톤을 자동으로 삭제합니다.

AddSingleton를 사용하여 싱글톤 서비스를 등록합니다. 싱글톤 서비스는 스레드로부터 안전해야 하며 상태 비저장 서비스에서 자주 사용됩니다.

요청을 처리하는 앱에서 싱글톤 서비스는 애플리케이션 종료 시 ServiceProvider가 삭제될 때 삭제됩니다. 앱이 종료될 때까지 메모리가 해제되지 않으므로 싱글톤 서비스에서 메모리 사용을 고려합니다.

서비스 등록 메서드

프레임워크는 특정 시나리오에 유용한 서비스 등록 확장 방법을 제공합니다.

메서드 자동
개체
폐기
여러
구현
인수 전달
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

예시:

services.AddSingleton<IMyDep, MyDep>();
아니요
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

예:

services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
Add{LIFETIME}<{IMPLEMENTATION}>()

예시:

services.AddSingleton<MyDep>();
없음 아니요
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

예:

services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
AddSingleton(new {IMPLEMENTATION})

예:

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
아니요 없음

형식 삭제에 대한 자세한 내용은 서비스의 삭제 섹션을 참조하세요.

구현 형식만으로 서비스를 등록하는 것은 동일한 구현 및 서비스 형식으로 해당 서비스를 등록하는 것과 같습니다. 따라서 명시적 서비스 형식을 사용하지 않는 메서드를 사용하여 서비스의 여러 구현을 등록할 수 없습니다. 이러한 메서드는 서비스의 여러 ‘인스턴스’를 등록할 수 있지만 모두 동일한 ‘구현’ 형식을 사용합니다.

위의 서비스 등록 메서드 중 하나를 사용하여 동일한 서비스 형식의 여러 서비스 인스턴스를 등록할 수 있습니다. 다음 예제에서는 IMessageWriter를 서비스 형식으로 사용하여 AddSingleton을 두 번 호출합니다. 두 번째 AddSingleton 호출은 IMessageWriter로 확인되면 이전 호출을 재정의하고 IEnumerable<IMessageWriter>를 통해 여러 서비스가 확인되면 이전 호출에 추가됩니다. 서비스는 IEnumerable<{SERVICE}>를 통해 해결될 때 등록된 순서로 나타납니다.

using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();

using IHost host = builder.Build();

_ = host.Services.GetService<ExampleService>();

await host.RunAsync();

위의 샘플 소스 코드는 IMessageWriter의 두 가지 구현을 등록합니다.

using System.Diagnostics;

namespace ConsoleDI.IEnumerableExample;

public sealed class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is LoggingMessageWriter);

        var dependencyArray = messageWriters.ToArray();
        Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
        Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
    }
}

ExampleService는 두 가지 생성자 매개 변수인 단일 IMessageWriterIEnumerable<IMessageWriter>를 정의합니다. 단일 IMessageWriter는 등록된 마지막 구현이고, IEnumerable<IMessageWriter>는 등록된 모든 구현을 나타냅니다.

또한 프레임워크에서는 아직 등록된 구현이 없는 경우에만 서비스를 등록하는 TryAdd{LIFETIME} 확장 메서드를 제공합니다.

다음 예제에서 AddSingleton을 호출하면 ConsoleMessageWriterIMessageWriter에 대한 구현으로 등록됩니다. TryAddSingleton에 대한 호출은 IMessageWriter에 이미 등록된 구현이 있으므로 아무런 효과가 없습니다.

services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();

TryAddSingleton은 이미 추가되었기 때문에 ‘시도’가 실패하므로 아무런 효과가 없습니다. ExampleService는 다음을 어설션합니다.

public class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is ConsoleMessageWriter);
        Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
    }
}

자세한 내용은 다음을 참조하세요.

TryAddEnumerable(ServiceDescriptor) 메서드는 ‘동일한 형식’의 구현이 아직 없는 경우에만 서비스를 등록합니다. 여러 서비스가 IEnumerable<{SERVICE}>를 통해 해결됩니다. 서비스를 등록할 때 동일한 형식 중 하나가 아직 추가되지 않은 경우 인스턴스를 추가합니다. 라이브러리 작성자는 컨테이너에 있는 특정 구현의 여러 복사본이 등록되지 않도록 TryAddEnumerable을 사용합니다.

다음 예제에서 TryAddEnumerable을 처음 호출하면 MessageWriterIMessageWriter1에 대한 구현으로 등록됩니다. 두 번째 호출에서는 IMessageWriter2에 대한 MessageWriter를 등록합니다. 세 번째 호출은 IMessageWriter1MessageWriter의 등록된 구현이 이미 있으므로 아무런 효과가 없습니다.

public interface IMessageWriter1 { }
public interface IMessageWriter2 { }

public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());

서비스 등록은 같은 형식의 여러 구현을 등록하는 경우를 제외하고 일반적으로 순서와 상관이 없습니다.

IServiceCollectionServiceDescriptor 개체의 컬렉션입니다. 다음 예에서는 ServiceDescriptor을 만든 후 추가하여 서비스를 등록하는 방법을 보여 줍니다.

string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
    typeof(IMessageWriter),
    _ => new DefaultMessageWriter(secretKey),
    ServiceLifetime.Transient);

services.Add(descriptor);

기본 제공 Add{LIFETIME} 메서드는 같은 방법을 사용합니다. 예를 들어 AddScoped 소스 코드를 참조하세요.

생성자 주입 동작

다음을 사용하여 서비스를 확인할 수 있습니다.

생성자에는 종속성 주입으로 제공되지 않는 인수를 사용할 수 있지만, 인수에 기본값을 할당해야 합니다.

IServiceProvider 또는 ActivatorUtilities로 서비스를 해결하는 경우 생성자 주입에 public 생성자가 필요합니다.

ActivatorUtilities로 서비스를 해결하는 경우 생성자 주입을 위해서는 적합한 생성자가 하나만 있어야 합니다. 생성자 오버로드가 지원되지만, 해당 인수가 모두 종속성 주입으로 처리될 수 있는 하나의 오버로드만 존재할 수 있습니다.

범위 유효성 검사

앱이 Development 환경에서 실행되고 CreateApplicationBuilder를 호출하여 호스트를 빌드할 때 기본 서비스 공급자는 다음 사항을 확인하기 위해 검사를 수행합니다.

  • 범위가 지정된 서비스는 루트 서비스 공급자에서 확인되지 않습니다.
  • 범위가 지정된 서비스는 싱글톤에 삽입되지 않습니다.

루트 서비스 공급자는 BuildServiceProvider를 호출할 때 만들어집니다. 루트 서비스 공급자의 수명은 공급자가 앱과 함께 시작되고 앱이 종료될 때 삭제되는 앱의 수명에 해당합니다.

범위가 지정된 서비스는 서비스를 만든 컨테이너에 의해 삭제됩니다. 범위가 지정된 서비스가 루트 컨테이너에서 만들어지는 경우 서비스의 수명은 사실상 싱글톤으로 승격됩니다. 해당 서비스는 앱이 종료될 때 루트 컨테이너에 의해서만 삭제되기 때문입니다. 서비스 범위의 유효성 검사는 BuildServiceProvider가 호출될 경우 이러한 상황을 감지합니다.

범위 시나리오

IServiceScopeFactory는 항상 singleton으로 등록되지만, IServiceProvider는 포함하는 클래스의 수명에 따라 달라질 수 있습니다. 예를 들어 범위에서 서비스를 확인하고 해당 서비스 중 하나에서 IServiceProvider를 사용하는 경우 이는 범위가 지정된 인스턴스가 됩니다.

IHostedService의 구현 내에 서비스 범위 지정을 두려면(예: BackgroundService) 생성자 주입을 통해 서비스 종속성을 주입하지 ‘마세요’. 대신 IServiceScopeFactory를 주입하고 범위를 만든 후 범위의 종속성을 확인하여 적절한 서비스 수명을 사용하도록 합니다.

namespace WorkerScope.Example;

public sealed class Worker(
    ILogger<Worker> logger,
    IServiceScopeFactory serviceScopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (IServiceScope scope = serviceScopeFactory.CreateScope())
            {
                try
                {
                    logger.LogInformation(
                        "Starting scoped work, provider hash: {hash}.",
                        scope.ServiceProvider.GetHashCode());

                    var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
                    var next = await store.GetNextAsync();
                    logger.LogInformation("{next}", next);

                    var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
                    await processor.ProcessAsync(next);
                    logger.LogInformation("Processing {name}.", next.Name);

                    var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
                    await relay.RelayAsync(next);
                    logger.LogInformation("Processed results have been relayed.");

                    var marked = await store.MarkAsync(next);
                    logger.LogInformation("Marked as processed: {next}", marked);
                }
                finally
                {
                    logger.LogInformation(
                        "Finished scoped work, provider hash: {hash}.{nl}",
                        scope.ServiceProvider.GetHashCode(), Environment.NewLine);
                }
            }
        }
    }
}

위의 코드에서 앱이 실행되는 동안 백그라운드 서비스는 다음과 같습니다.

  • IServiceScopeFactory에 종속됩니다.
  • 추가 서비스를 확인하기 위한 IServiceScope를 만듭니다.
  • 사용할 범위가 지정된 서비스를 확인합니다.
  • 개체 처리에 대한 작업을 수행한 후 해당 개체를 릴레이하고 마지막으로 해당 개체를 처리된 것으로 표시합니다.

샘플 소스 코드에서 IHostedService 구현이 범위가 지정된 서비스 수명의 이점을 활용하는 방법을 확인할 수 있습니다.

키 지정 서비스

.NET 8부터 키 기반 서비스 등록 및 조회가 지원됩니다. 즉, 서로 다른 키로 여러 서비스를 등록하고 조회에 이 키를 사용할 수 있습니다.

예를 들어, 인터페이스 IMessageWriter(MemoryMessageWriterQueueMessageWriter)의 서로 다른 구현이 있는 경우를 생각해 보세요.

키를 매개 변수로 지원하는 서비스 등록 메서드(앞서 본)의 오버로드를 사용하여 이러한 서비스를 등록할 수 있습니다.

services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");

keystring으로 제한되지 않고 형식이 Equals를 올바르게 구현하는 한 원하는 모든 object일 수 있습니다.

IMessageWriter를 사용하는 클래스의 생성자에서 FromKeyedServicesAttribute를 추가하여 확인할 서비스의 키를 지정합니다.

public class ExampleService
{
    public ExampleService(
        [FromKeyedServices("queue")] IMessageWriter writer)
    {
        // Omitted for brevity...
    }
}

참고 항목