OOP Course
Сьогодні

Підрозділ 21.1

Generic Host — архітектура та життєвий цикл додатку

21.1. Generic Host — архітектура та життєвий цикл додатку Будь який реальний додаток потребує більше, ніж просто виконання рядків коду зверху вниз. Йому потрібно запуститися, налаштуватись, підключитись до баз

21.1. Generic Host — архітектура та життєвий цикл додатку

Будь-який реальний додаток потребує більше, ніж просто виконання рядків коду зверху вниз. Йому потрібно запуститися, налаштуватись, підключитись до баз даних, логерів, черг повідомлень, дочекатись сигналу зупинки і правильно завершити роботу — зберегти стан, закрити з'єднання, завершити незакінчені операції. Якщо все це реалізовувати вручну у кожному проекті, код Main() перетвориться на величезний монолітний метод, а будь-яка зміна в порядку ініціалізації ризикує зламати всю систему.

Саме для вирішення цієї проблеми Microsoft у .NET Core 2.1 (2018) ввів концепцію Generic Host (Microsoft.Extensions.Hosting). Generic Host — це інфраструктурна оболонка, що бере на себе відповідальність за управління життєвим циклом додатку: запуск, виконання фонових сервісів, обробку сигналів завершення і коректний shutdown. До цього аналогічний механізм існував лише для ASP.NET Core (WebHost), але з появою Generic Host він став доступним для будь-якого типу додатків — консольних, Windows Services, worker services, мікросервісів.

Проблема: «розсипаний» Main без хоста

Розглянемо типову еволюцію консольного додатку для клінічної системи. Спочатку все здається простим:

Цей код має щонайменше п'ять структурних проблем. Жодна з них не критична сама по собі — але разом вони перетворюють додаток на крихку конструкцію, яку важко розширювати й майже неможливо тестувати.

Що таке Generic Host

Generic Host — це об'єкт типу IHost, що виконує три ролі одночасно:

1. Service Container (DI-контейнер) — зберігає реєстрацію всіх сервісів і відповідає за їхнє створення та управління їхнім часом життя. Замість того, щоб кожен клас сам створював свої залежності через new, він отримує їх через конструктор — а контейнер знає, як їх побудувати.

2. Configuration Provider — агрегує конфігурацію з різних джерел: appsettings.json, змінні середовища, аргументи командного рядка, секрети — і надає єдиний уніфікований доступ через IConfiguration.

3. Lifecycle Manager — відстежує стан додатку, запускає фонові сервіси (IHostedService), перехоплює системні сигнали (SIGTERM, Ctrl+C) і гарантує коректний порядок зупинки компонентів.

┌─────────────────────────── IHost ────────────────────────────────┐
│                                                                   │
│   ┌─────────────────┐   ┌──────────────────┐   ┌─────────────┐  │
│   │ IServiceProvider│   │ IConfiguration   │   │  Lifecycle  │  │
│   │ (DI Container)  │   │ (Config Sources) │   │  Manager    │  │
│   └─────────────────┘   └──────────────────┘   └─────────────┘  │
│          │                       │                     │          │
│    реєстрація             appsettings.json        Start / Stop   │
│    та resolve             env variables          IHostedService   │
│    сервісів               cmd args               CancellationToken│
└─────────────────────────────────────────────────────────────────┘

Архітектура: Builder Pattern для хоста

Generic Host будується за Builder Pattern: спочатку конфігурується IHostBuilder, потім викликається .Build() і отримується IHost. Реальний API виглядає так:

// Реальний код з Microsoft.Extensions.Hosting (не runnable в браузері)
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        // Реєструємо сервіси
        services.AddSingleton<DatabaseConnection>();
        services.AddScoped<AppointmentService>();
        services.AddTransient<ReportService>();
    })
    .Build();

await host.RunAsync();

Host.CreateDefaultBuilder() — це фабричний метод, що автоматично налаштовує:

  • завантаження appsettings.json та appsettings.{Environment}.json
  • читання змінних середовища з префіксом DOTNET_
  • читання аргументів командного рядка
  • базове логування в консоль та Debug
  • встановлення кореневого каталогу додатку

Реалізуємо спрощений хост «з нуля»

Щоб зрозуміти, що відбувається всередині IHost, побудуємо власну спрощену реалізацію. Це найкращий спосіб зрозуміти будь-яку інфраструктурну абстракцію — побудувати її самостійно.

Ключові компоненти реального Generic Host

Розглянемо API, що ви побачите у реальному коді .NET-проектів:

IHost — центральний інтерфейс. Надає доступ до IServiceProvider через властивість Services. Методи StartAsync() / StopAsync() / RunAsync() керують lifecycle.

IHostBuilder — будівельник хоста. Надає fluent API для конфігурації:

  • ConfigureServices(Action<IServiceCollection>) — реєстрація сервісів
  • ConfigureAppConfiguration(Action<IConfigurationBuilder>) — додаткові джерела конфігурації
  • ConfigureLogging(Action<ILoggingBuilder>) — налаштування логування
  • UseEnvironment(string) — встановлення середовища (Development, Production)

IHostedService — інтерфейс фонового сервісу. Два методи: StartAsync(CancellationToken) і StopAsync(CancellationToken). Хост запускає всі зареєстровані IHostedService при старті і зупиняє у зворотному порядку при shutdown.

BackgroundService — абстрактний базовий клас, що реалізує IHostedService. Достатньо перевизначити один метод: ExecuteAsync(CancellationToken stoppingToken) — і писати в ньому нескінченний цикл або очікування.

Generic Host — архітектура та потік запуску

Порядок старту та shutdown

Розуміння порядку є критичним для коректної роботи:

Запуск:

  1. IHostBuilder.Build() — збирається DI-контейнер, перевіряється реєстрація
  2. IHost.StartAsync() — послідовно викликає StartAsync() у кожного IHostedService у порядку реєстрації
  3. Хост входить у стан «running» і чекає сигналу зупинки

Shutdown (при Ctrl+C, SIGTERM або явному виклику StopAsync()):

  1. IHostApplicationLifetime сповіщає підписників ApplicationStopping
  2. IHost.StopAsync() — послідовно викликає StopAsync() у кожного IHostedService у зворотному порядку
  3. IHostApplicationLifetime сповіщає підписників ApplicationStopped
  4. DI-контейнер диспозиться — викликається Dispose() на всіх IDisposable singleton-сервісах

Зворотний порядок shutdown є навмисним і важливим: перший зупинений сервіс може залежати від ресурсів, що надають пізніше зупинені сервіси. Наприклад, EmailSender (зупиняється першим) може надсилати Shutdown notification через SmtpClient (зупиняється останнім).

Generic Host vs WebApplication

Починаючи з .NET 6, для ASP.NET Core проектів з'явився WebApplication — ще більш спрощений API (var app = WebApplication.Create()). Він побудований поверх Generic Host і фактично є синтаксичним цукром для найпоширенішого сценарію — веб-додатків. Для консольних програм, Worker Services, мікросервісів без HTTP — Generic Host залишається правильним вибором.

Generic Host WebApplication
Тип додатку будь-який ASP.NET Core
NuGet пакет Microsoft.Extensions.Hosting Microsoft.AspNetCore.App
HTTP pipeline немає є (middleware)
Запуск host.RunAsync() app.RunAsync()
IServiceCollection ConfigureServices() builder.Services

Підсумок

Generic Host вирішує проблему «розсипаного Main» шляхом введення чіткої відповідальності: хост знає про lifecycle, конфігурацію і контейнер сервісів — а бізнес-код нічого цього не знає і не зобов'язаний знати. Це пряме втілення принципів SRP та DIP зі SOLID: кожна частина системи відповідає за одне, і залежності течуть через абстракції.

Розроблено Tomka Yurii · © 2026 ·