Підрозділ 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) — і писати в ньому нескінченний цикл або очікування.

Порядок старту та shutdown
Розуміння порядку є критичним для коректної роботи:
Запуск:
IHostBuilder.Build()— збирається DI-контейнер, перевіряється реєстраціяIHost.StartAsync()— послідовно викликаєStartAsync()у кожногоIHostedServiceу порядку реєстрації- Хост входить у стан «running» і чекає сигналу зупинки
Shutdown (при Ctrl+C, SIGTERM або явному виклику StopAsync()):
IHostApplicationLifetimeсповіщає підписниківApplicationStoppingIHost.StopAsync()— послідовно викликаєStopAsync()у кожногоIHostedServiceу зворотному порядкуIHostApplicationLifetimeсповіщає підписниківApplicationStopped- DI-контейнер диспозиться — викликається
Dispose()на всіхIDisposablesingleton-сервісах
Зворотний порядок 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: кожна частина системи відповідає за одне, і залежності течуть через абстракції.