Microsoft.Extensions.Hosting, 호스팅 모델(Hosting Model)

개요

Microsoft.Extensions.Hosting 네임스페이스는 .NET 애플리케이션의 호스팅 모델(Hosting Model)을 제공하는 핵심 구성 요소입니다.

Host 클래스는 이 모델의 시작점 역할을 하며, 애플리케이션의 생명 주기(lifecycle), 구성(configuration), 의존성 주입(dependency injection, DI), 로깅(logging) 등 다양한 인프라 서비스를 중앙 집중식으로 관리합니다.

이 호스팅 모델은 ASP.NET Core 애플리케이션에서 시작되었지만, 현재는 콘솔 애플리케이션, 백그라운드 서비스, Windows 서비스, WPF 애플리케이션 등 다양한 종류의 .NET 애플리케이션에서 표준적인 방식으로 사용되고 있습니다.

1. Host 클래스의 목적과 역할

Host 클래스는 특정 애플리케이션 유형에 구애받지 않고, 공통적인 인프라 기능을 쉽게 통합하고 관리할 수 있도록 설계되었습니다.

  • 생명 주기 관리: 애플리케이션의 시작(StartAsync) 및 종료(StopAsync)를 관리하고, 이 과정에서 필요한 서비스들이 올바른 순서로 초기화되고 정리될 수 있도록 합니다.
  • 의존성 주입 (DI): 애플리케이션 전반에 걸쳐 강력한 DI 컨테이너를 제공하여, 서비스 간의 느슨한 결합을 촉진하고 테스트 용이성을 향상시킵니다.
  • 구성 (Configuration): appsettings.json, 환경 변수, 명령줄 인수 등 다양한 소스에서 설정을 로드하고 관리하는 일관된 방법을 제공합니다.
  • 로깅 (Logging): 다양한 로깅 프로바이더(콘솔, 파일, 데이터베이스 등)를 쉽게 통합하고, 로깅 레벨을 유연하게 제어할 수 있도록 합니다.
  • 백그라운드 서비스 (IHostedService): 장시간 실행되는 백그라운드 작업을 호스트의 생명 주기에 통합하여 관리할 수 있도록 합니다. 이는 API를 제공하지 않고 단순히 작업을 수행하는 서비스에 특히 유용합니다.

2. Host 클래스 핵심 인터페이스 및 메서드

Host 클래스 자체보다는, 이와 관련된 IHostIHostBuilder 인터페이스를 이해하는 것이 중요합니다.

(1) IHostBuilder 인터페이스

IHostBuilder는 호스트를 구성(Configure)하는 데 사용되는 인터페이스입니다.

Host.CreateDefaultBuilder() 메서드가 반환하는 객체가 바로 IHostBuilder 타입입니다.

이 빌더를 통해 다음을 정의할 수 있습니다:

  • ConfigureAppConfiguration:
    설정 소스(파일, 환경 변수 등)를 추가하거나 커스터마이징합니다.
.ConfigureAppConfiguration((context, config) =>
{
    config.AddJsonFile("custom.json", optional: true)
          .AddEnvironmentVariables()
          .AddCommandLine(args);
})
  • ConfigureLogging:
    로깅 프로바이더(콘솔, 디버그, 파일 등)를 추가하고 로깅 필터링 규칙을 정의합니다.
.ConfigureLogging((context, logging) =>
{
    logging.AddConsole()
           .AddDebug()
           .SetMinimumLevel(LogLevel.Information);
})
  • ConfigureServices:
    의존성 주입 컨테이너에 애플리케이션의 서비스들을 등록합니다.
    AddSingleton, AddScoped, AddTransient 등의 메서드를 사용하여 서비스의 생명 주기를 지정할 수 있습니다.
.ConfigureServices((context, services) =>
{
    services.AddSingleton<ISingletonService, SingletonService>();
    services.AddScoped<IScopedService, ScopedService>();
    services.AddTransient<ITransientService, TransientService>();
    services.AddHostedService<MyBackgroundService>();
})
  • UseConsoleLifetime:
    콘솔 애플리케이션의 경우, Ctrl+C와 같은 콘솔 시그널을 감지하여 호스트를 정상적으로 종료하도록 설정합니다.
    (이는 Host.CreateDefaultBuilder()에 의해 기본적으로 포함됩니다.)
  • Build():
    IHostBuilder에 정의된 모든 구성을 기반으로 최종 IHost 인스턴스를 생성합니다.

(2) IHost 인터페이스

IHost빌드된 호스트 인스턴스를 나타내며, 실제로 애플리케이션을 실행하고 관리하는 데 사용됩니다.

주요 메서드는 다음과 같습니다

  • Services:
    등록된 모든 서비스 인스턴스에 접근할 수 있는 IServiceProvider를 제공합니다.
    이를 통해 런타임에 필요한 서비스를 resolve(가져오기)할 수 있습니다.
  • StartAsync(CancellationToken cancellationToken):
    호스트를 비동기적으로 시작합니다. 이 메서드가 호출되면 등록된 모든 IHostedService 인스턴스의 StartAsync 메서드가 호출됩니다.
  • StopAsync(CancellationToken cancellationToken):
    호스트를 비동기적으로 종료합니다. 등록된 모든 IHostedService 인스턴스의 StopAsync 메서드가 호출되어 리소스를 정리할 기회를 줍니다.
  • RunAsync(CancellationToken cancellationToken = default):
    StartAsync를 호출하고, 호스트가 종료될 때까지 대기한 후 StopAsync를 호출합니다.
    대부분의 콘솔 애플리케이션에서 메인 실행 루프로 사용됩니다.

(3) IHostedService 인터페이스

IHostedService는 호스트의 생명 주기에 통합되어 백그라운드 작업을 수행하는 서비스들을 정의하는 인터페이스입니다.

StartAsyncStopAsync 두 가지 메서드를 구현해야 합니다.

  • StartAsync(CancellationToken cancellationToken):
    호스트가 시작될 때 호출됩니다. 장시간 실행되는 작업을 시작하는 데 사용됩니다.
  • StopAsync(CancellationToken cancellationToken):
    호스트가 종료될 때 호출됩니다. StartAsync에서 시작된 작업을 정상적으로 중지하고 리소스를 정리하는 데 사용됩니다.

3. Host.CreateDefaultBuilder() 메서드의 이점

이 메서드는 많은 “관습(conventions)”을 제공하여 애플리케이션 초기 설정을 간소화하는 장점이 있습니다.

  • 일관성:
    모든 .NET 애플리케이션 유형(웹, 콘솔, 백그라운드 서비스)에서 동일한 방식으로 인프라를 설정할 수 있게 하여 코드의 일관성을 유지합니다.
  • 생산성:
    설정, 로깅, DI 등 반복적인 초기화 코드를 작성할 필요 없이 바로 비즈니스 로직에 집중할 수 있도록 합니다.
  • 확장성:
    기본 설정을 사용하면서도 ConfigureAppConfiguration, ConfigureServices 등의 메서드를 통해 언제든지 필요에 따라 커스터마이징하고 확장할 수 있습니다.
  • 테스트 용이성:
    DI 컨테이너를 중심으로 설계되어, 단위 테스트 및 통합 테스트에서 의존성을 쉽게 Mocking하거나 대체할 수 있습니다.
    참고: Mocking – 테스트 코드 작성 시 가짜 객체(Mock)를 만들어 실제 객체 대신 사용하는 기법

예시 코드: 콘솔 애플리케이션에서 Host 사용하기

콘솔 애플리케이션에서 Host 클래스를 사용하여 백그라운드 서비스(카운터)를 실행하고, 설정 및 로깅을 사용하는 예제

  • Microsoft.Extensions.Hosting NuGet 패키지를 설치
dotnet add package Microsoft.Extensions.Hosting
  • appsettings.json 파일을 추가하고 내용을 작성합니다.
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AppConfig": {
    "IntervalSeconds": 2
  }
}
  • Services/MyBackgroundService.cs (커스텀 IHostedService 구현):
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration; // IConfiguration 주입을 위해 추가
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MyConsoleApp.Services
{
    // IHostedService를 구현하여 호스트의 생명 주기에 통합될 백그라운드 서비스를 정의합니다.
    public class MyBackgroundService : IHostedService, IDisposable
    {
        private readonly ILogger<MyBackgroundService> _logger;
        private readonly IConfiguration _configuration;
        private Timer? _timer;
        private int _executionCount = 0;
        private int _intervalSeconds;

        public MyBackgroundService(ILogger<MyBackgroundService> logger, IConfiguration configuration)
        {
            _logger = logger;
            _configuration = configuration;

            // appsettings.json에서 설정 값 읽기
            _intervalSeconds = _configuration.GetValue<int>("AppConfig:IntervalSeconds", 5); // 기본값 5초
        }

        // 호스트가 시작될 때 호출됩니다.
        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("MyBackgroundService가 시작되었습니다.");

            // 타이머를 설정하여 지정된 간격마다 DoWork 메서드를 호출합니다.
            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(_intervalSeconds));

            return Task.CompletedTask; // 비동기 작업 시작을 알리고 즉시 반환
        }

        // 타이머에 의해 주기적으로 호출되는 실제 작업 메서드
        private void DoWork(object? state)
        {
            var count = Interlocked.Increment(ref _executionCount);
            _logger.LogInformation(
                "MyBackgroundService가 {count}번째 작업을 수행 중입니다. 현재 시간: {time}",
                count, DateTimeOffset.Now);
        }

        // 호스트가 종료될 때 호출됩니다.
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("MyBackgroundService가 중지되고 있습니다.");

            // 타이머를 중지하고 리소스를 해제합니다.
            // Change(Timeout.Infinite, 0)은 타이머를 비활성화하는 표준 방법입니다.
            _timer?.Change(Timeout.Infinite, 0);

            _logger.LogInformation("MyBackgroundService가 중지되었습니다.");
            return Task.CompletedTask; // 작업 완료를 알립니다.
        }

        // 리소스 해제를 위한 Dispose 메서드 (IHostedService는 IDisposable을 구현하는 경우가 많음)
        public void Dispose()
        {
            _timer?.Dispose();
        }
    }
}
  • Program.cs 구현:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MyConsoleApp.Services; // MyBackgroundService를 사용하기 위해 추가
using System;
using System.Threading.Tasks;

namespace MyConsoleApp
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            Console.WriteLine("콘솔 애플리케이션 호스트가 시작됩니다. 종료하려면 Ctrl+C를 누르세요.");

            // 1. IHostBuilder 인스턴스 생성: Host.CreateDefaultBuilder()는 기본적인 구성(config, logging, DI)을 설정합니다.
            var builder = Host.CreateDefaultBuilder(args)
                // 2. 서비스 컨테이너 구성: 의존성 주입을 위한 서비스들을 등록합니다.
                .ConfigureServices((hostContext, services) =>
                {
                    // MyBackgroundService를 IHostedService로 등록합니다.
                    // 호스트가 시작될 때 StartAsync가 호출되고, 종료될 때 StopAsync가 호출됩니다.
                    services.AddHostedService<MyBackgroundService>();

                    // 여기에 다른 서비스들도 등록할 수 있습니다. 예:
                    // services.AddSingleton<IMyService, MyService>();
                    // services.AddScoped<IOtherService, OtherService>();
                })
                // 3. 로깅 구성 (선택 사항: CreateDefaultBuilder가 기본 설정 제공하지만, 커스터마이징 가능)
                .ConfigureLogging(logging =>
                {
                    logging.ClearProviders(); // 기본 로깅 프로바이더를 지우고 새로 추가하거나
                    logging.AddConsole();    // 콘솔 로거를 추가합니다.
                    // logging.AddDebug();    // 디버그 로거 추가
                });
                // 4. 기타 구성 (선택 사항: 파일 경로, 환경 등)
                // .UseContentRoot(Directory.GetCurrentDirectory())
                // .UseEnvironment("Development")
                // .ConfigureAppConfiguration((hostingContext, config) => {
                //    config.AddJsonFile("custom.json", optional: true);
                // });

            // 5. IHost 인스턴스 빌드: 구성된 빌더를 바탕으로 실제 호스트를 생성합니다.
            var host = builder.Build();

            // 6. 호스트 실행: 등록된 모든 IHostedService를 시작하고 애플리케이션 종료를 대기합니다.
            // Ctrl+C 시그널을 감지하여 정상적으로 종료됩니다.
            await host.RunAsync();

            Console.WriteLine("콘솔 애플리케이션 호스트가 종료되었습니다.");
        }
    }
}

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤