Minimal API

Swagger / Swashbuckle у Minimal API: окремий класичний шлях

Окремий матеріал про Swagger UI та Swashbuckle в ASP.NET Core Minimal API: коли використовувати, як підключити в .NET 8, як працює AddEndpointsApiExplorer, AddSwaggerGen, UseSwagger, UseSwaggerUI та як документувати endpoint-и.

Swagger / Swashbuckle у Minimal API: окремий класичний шлях

У попередній статті ми розглянули сучасний шлях Minimal API + OpenAPI + Scalar. Але в реальних проєктах ви дуже часто зустрінете інший стек: Swagger UI + Swashbuckle. Це не просто історичний артефакт. Це окремий, дуже поширений спосіб генерувати OpenAPI-документ і показувати його через вбудований UI.Цей матеріал потрібен з двох причин:
  1. велика кількість існуючих Minimal API проєктів уже побудована саме на Swashbuckle;
  2. терміни Swagger, Swashbuckle, Swagger UI і OpenAPI постійно змішують, і без окремого розбору в голові лишається плутанина.
Для нових проєктів на .NET 9+ базовим шляхом у цьому курсі лишається зв'язка AddOpenApi() + Scalar. За актуальною документацією Microsoft, Swashbuckle не є доступним шляхом для .NET 9+, тому ця стаття свідомо орієнтована на .NET 8 та підтримку існуючих систем.

Що розберемо

  • різницю між Swagger UI, Swashbuckle і OpenAPI;
  • повний відтворюваний проєкт Minimal API на .NET 8;
  • AddEndpointsApiExplorer, AddSwaggerGen, UseSwagger, UseSwaggerUI;
  • Fluent-опис endpoint-ів без атрибутів;
  • XML comments, route config, безпеку та кастомізацію UI.

Коли це треба

  • ви підтримуєте API на .NET 8;
  • команда вже використовує Swashbuckle.AspNetCore;
  • у вас є Swagger UI і ви хочете зрозуміти, як він влаштований;
  • потрібно впевнено читати legacy або чинний enterprise-код.

Що буде в кінці

  • swagger/v1/swagger.json віддає OpenAPI-документ;
  • /swagger відкриває інтерактивний Swagger UI;
  • endpoint-и Minimal API мають summaries, descriptions і responses;
  • ви чітко розумієте, де закінчується OpenAPI і де починається Swagger tooling.

1. OpenAPI, Swagger UI, Swashbuckle: хто за що відповідає

Почнемо з найголовнішого розмежування.

ТермінЩо це такеРоль
OpenAPIСтандарт специфікаціїФормат контракту API
Swagger UIWeb UIПоказує OpenAPI-документ у браузері
Swashbuckle.NET toolingГенерує OpenAPI-документ і вбудовує Swagger UI в ASP.NET Core

Інженерна логіка така:

  1. Minimal API endpoint-и містять metadata.
  2. Swashbuckle читає цю metadata і генерує swagger.json.
  3. Swagger UI в браузері читає цей JSON і малює інтерфейс.
Loading diagram...
flowchart LR
    A["Minimal API routes"] --> B["ApiExplorer metadata"]
    B --> C["Swashbuckle generator"]
    C --> D["/swagger/v1/swagger.json"]
    D --> E["Swagger UI /swagger"]
Swagger UI сам по собі не знає, які у вас є endpoint-и. Йому потрібен уже готовий OpenAPI JSON. Тому при діагностиці проблем важливо відділяти: «зламався генератор документа» від «зламався UI, який цей документ показує».

2. Коли обирати Swagger окремо від Scalar

Після статті про Scalar природно виникає питання: навіщо ще окремо вивчати Swagger?

Відповідь прагматична:

  • підтримка існуючих .NET 8 проєктів;
  • традиційний стек, який досі масово використовується;
  • велика кількість прикладів у legacy-коді, блогах і корпоративних шаблонах;
  • інтеграція в історичні пайплайни документації.

Висновок тут не ідеологічний, а прикладний:

  • якщо система вже на Swashbuckle, потрібно вміти з ним працювати;
  • якщо ви починаєте новий .NET 9+ проєкт, то Scalar зазвичай краще відповідає сучасному стеку цього курсу.

3. Відтворюваний проєкт на .NET 8

Щоб матеріал був не абстрактним, створимо окремий невеликий Minimal API-проєкт під Swagger.

Крок 1: Створення

mkdir MinimalApiSwaggerDemo
cd MinimalApiSwaggerDemo
dotnet new web --framework net8.0

Крок 2: Пакет Swashbuckle

dotnet add package Swashbuckle.AspNetCore

Крок 3: Що ми побудуємо

Так само, як у статті про Scalar, зробимо невеликий каталог товарів:

  • GET /api/products
  • GET /api/products/{id}
  • POST /api/products
  • PUT /api/products/{id}
  • DELETE /api/products/{id}

Наприкінці перевіримо два URL:

  • /swagger/v1/swagger.json
  • /swagger
dotnet run
$ dotnet run
Building...
Now listening on: https://localhost:7118
Application started. Press Ctrl+C to shut down.

4. Структура проєкту


5. Що робить AddEndpointsApiExplorer()

Це один із тих рядків, які копіюють майже всі, але пояснюють далеко не всі.

builder.Services.AddEndpointsApiExplorer();

Для Minimal API цей сервісовий виклик критично важливий, бо саме він дає ApiExplorer знання про endpoint-и, створені через MapGet, MapPost, MapPut і так далі.

Якщо його прибрати:

  • застосунок продовжить працювати;
  • самі endpoint-и нікуди не зникнуть;
  • але Swashbuckle перестане «бачити» Minimal API-маршрути так, як очікує генератор документації.
У controller-based Web API зв'язка з ApiExplorer виглядає інакше, тому розробники часто переносять інтуїцію з контролерів у Minimal API й недооцінюють роль AddEndpointsApiExplorer(). Для Minimal API це не декоративний, а структурний рядок.

6. Що робить AddSwaggerGen()

Це вже серце інтеграції.

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new()
    {
        Title = "Minimal API Swagger Demo",
        Version = "v1"
    });
});

Саме тут ми кажемо:

  • що OpenAPI-документ взагалі треба згенерувати;
  • як він називається;
  • яку версію документа ми реєструємо;
  • які додаткові налаштування застосувати.

Найважливіші можливості SwaggerGen

SwaggerDoc(...)
method
Реєструє документ з ім'ям на кшталт v1.
IncludeXmlComments(...)
method
Підтягує XML documentation comments з компіляції.
CustomSchemaIds(...)
method
Корисно, коли в проєкті є однакові імена типів у різних namespace.
SupportNonNullableReferenceTypes()
method
Допомагає точніше відобразити nullable/non-nullable reference types.
OperationFilter / DocumentFilter / SchemaFilter
extension points
Дають тонку кастомізацію документа, операцій і схем.

7. UseSwagger() і UseSwaggerUI(): чому це два різні виклики

У багатьох Program.cs вони стоять поряд, тому створюється ілюзія, що це «одна функція у двох рядках». Насправді це дві різні відповідальності.

app.UseSwagger();

Цей middleware віддає OpenAPI JSON, зазвичай на маршруті:

  • /swagger/v1/swagger.json

Тобто він відповідає за машиночитаний документ.

Простими словами:

  • UseSwagger() = «віддай JSON»;
  • UseSwaggerUI() = «віддай інтерфейс, який цей JSON показує».

8. Документування Minimal API endpoint-ів під Swagger

Найважливіше інженерне зауваження: Swagger UI не живе окремим життям. Якщо ви хочете, щоб UI був багатим і точним, metadata треба закладати в самі endpoint-и.

У Minimal API для цього достатньо Fluent API:

group.MapPost("/", CreateProduct)
    .WithName("CreateProduct")
    .WithSummary("Створити товар")
    .WithDescription("Створює товар і повертає 201 Created.")
    .Accepts<CreateProductRequest>("application/json")
    .Produces<ProductResponse>(StatusCodes.Status201Created)
    .ProducesValidationProblem(StatusCodes.Status422UnprocessableEntity)
    .ProducesProblem(StatusCodes.Status500InternalServerError)
    .WithOpenApi();

Що з цього реально потрапляє в Swagger UI

Fluent APIЩо ви побачите в Swagger UI
WithSummary(...)короткий заголовок операції
WithDescription(...)розгорнутий опис
WithTags(...)групування операцій
Accepts<T>(...)request body schema
Produces<T>(...)schema успішної відповіді
Produces(...)статус-коди без тіла
ProducesProblem(...)ProblemDetails-style error
WithName(...) + WithOpenApi()стабільні operation metadata
Якщо ви вже читали статтю про Scalar, важливо побачити головне: опис endpoint-ів майже той самий. Змінюється переважно не спосіб мислення про metadata, а інструмент, який рендерить документ у UI.

9. XML comments: коли вони ще корисні

Swagger historically дуже любить XML comments, і в екосистемі .NET 8 ви будете бачити їх часто.

Увімкнення в .csproj

<GenerateDocumentationFile>true</GenerateDocumentationFile>

Підключення в AddSwaggerGen

var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);

if (File.Exists(xmlPath))
{
    options.IncludeXmlComments(xmlPath);
}

Навіщо це потрібно

XML comments підтягують:

  • summaries;
  • descriptions;
  • коментарі до моделей і властивостей.

Але тут є важлива практична межа.

Практичний висновок: у Minimal API Fluent API зазвичай читається краще, але XML comments усе ще залишаються корисним додатковим шаром, особливо для DTO та підтримки старих проєктів.


10. Кастомізація Swagger UI

Навіть базовий Swagger UI уже робочий, але в реальних системах його часто підлаштовують під інфраструктуру або UX-компроміси.

app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/swagger/v1/swagger.json", "Demo v1");
});

На що звернути увагу

  • SwaggerEndpoint(...) повинен вказувати на правильний JSON.
  • RoutePrefix = string.Empty відкриває UI на корені сайту.
  • відносний шлях ./swagger/v1/swagger.json корисний за reverse proxy або віртуальних директорій.
Одна з найчастіших production-помилок: у девелопменті все працює на абсолютному /swagger/v1/swagger.json, а під reverse proxy або нестандартним base path UI перестає знаходити документ. У таких випадках відносний ./ шлях часто безпечніший.

11. Безпека Swagger endpoint-ів

Помилка початківців: вважати, що якщо ваші API endpoint-и захищені, то documentation UI автоматично теж захищений. Це не так.

Окремо подумайте про:

  • swagger.json;
  • /swagger;
  • доступність UI лише в development;
  • потребу в авторизації.

Найпростіший варіант

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

Це стандартний стартовий компроміс: документація доступна лише в development.

Захистити сам JSON-маршрут

Офіційні приклади Microsoft також показують варіант із:

app.MapSwagger().RequireAuthorization();

Такий підхід корисний, якщо UI або документ мають бути доступні тільки аутентифікованим користувачам.


12. Типові помилки зі Swagger у Minimal API


13. Практичні завдання

Рівень 1: Базовий

Завдання 17.1: Підніміть базовий Swagger UI

Відтворіть проєкт зі статті й перевірте:

  • /swagger/v1/swagger.json
  • /swagger

Завдання 17.2: Додайте ще один endpoint

Створіть GET /api/products/active, опишіть його через WithSummary, WithDescription і Produces.

Рівень 2: Metadata

Завдання 17.3: Покращіть POST

Для POST /api/products додайте:

  • приклад payload;
  • ще один статус-код помилки;
  • детальніший опис сценарію.

Завдання 17.4: Підключіть XML comments

Додайте XML comments до DTO і перевірте, що частина описів з'явилась у Swagger UI.

Рівень 3: Інфраструктура

Завдання 17.5: RoutePrefix

Перенесіть Swagger UI на корінь сайту через RoutePrefix = string.Empty.

Завдання 17.6: Захистіть swagger endpoints

Налаштуйте авторизацію так, щоб доступ до /swagger і /swagger/v1/swagger.json був лише після аутентифікації.


14. Резюме

Swagger не дорівнює OpenAPI

OpenAPI — це контракт. Swagger UI — це UI. Swashbuckle — це .NET tooling, яке допомагає згенерувати контракт і показати його.

Для .NET 8 це досі важливо

Swashbuckle залишається важливим для підтримки існуючих Minimal API систем і для розуміння legacy-коду.

Metadata усе ще вирішує все

Якість Swagger UI прямо залежить від того, наскільки добре ви описали endpoint-и через Fluent API або інші джерела metadata.

Scalar і Swagger варто знати обидва

Scalar потрібен для сучасного стеку курсу. Swagger потрібен для ширшої практики і підтримки вже існуючих .NET 8 рішень.