Minimal API и OpenAPI
Minimal API и OpenAPI
Minimal API — маршруты без контроллеров
В 4511 вы видели app.MapGet("/health", ...). Это и есть Minimal API: HTTP-обработчик объявляется рядом с настройкой приложения, без класса XxxController.
OpenAPI — стандарт описания API (пути, методы, схемы JSON). Из него строят Swagger UI — страницу в браузере, где можно нажать «Try it out» и отправить запрос.
Здесь — структура проекта, группы маршрутов, типизированные ответы, DI, фильтры и документация.
Словарь
| Термин | Простыми словами |
|---|---|
| Эндпоинт | Один маршрут + метод (например GET /api/notes). |
| MapGroup | Общий префикс и настройки для нескольких маршрутов. |
| Results / TypedResults | Фабрики ответов: 200, 201, 404, 400 с телом. |
| Problem Details | Стандартный JSON ошибки (RFC 7807). |
| Endpoint filter | Код до/после handler (лог, auth) — аналог action filter. |
| Swashbuckle | Популярный пакет Swagger для ASP.NET Core. |
Minimal API или контроллеры?
| Minimal API | Контроллеры | |
|---|---|---|
| Объём кода | Меньше файлов | Папка Controllers/, атрибуты |
| Сложная логика | Сервисы + тонкие handlers | Привычная структура MVC |
| OpenAPI | Produces, WithSummary, TypedResults | Часто богаче из коробки |
| Команда | Микросервисы, BFF | Крупные API-проекты |
В одном приложении: MapControllers() + MapGroup("/api/v1").
Стартовый проект
dotnet new web -n MinimalNotes -o MinimalNotes
cd MinimalNotes
dotnet add package Swashbuckle.AspNetCore
Шаблон web — чистый Minimal API без контроллеров по умолчанию.
Эндпоинты и DTO
Models/NoteDto.cs:
namespace MinimalNotes.Models;
public record Note(int Id, string Text, DateTime Created);
public record CreateNoteRequest(string Text);
record — компактный тип для JSON; свойства задаются в конструкторе.
Program.cs:
using MinimalNotes.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new() { Title = "Minimal Notes API", Version = "v1" });
});
var notes = new List<Note>();
var nextId = 1;
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
var api = app.MapGroup("/api/notes")
.WithTags("Notes");
api.MapGet("/", () => Results.Ok(notes.OrderByDescending(n => n.Created)));
api.MapPost("/", (CreateNoteRequest req) =>
{
if (string.IsNullOrWhiteSpace(req.Text))
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["Text"] = ["Текст обязателен"]
});
var note = new Note(nextId++, req.Text.Trim(), DateTime.UtcNow);
notes.Add(note);
return Results.Created($"/api/notes/{note.Id}", note);
});
api.MapGet("/{id:int}", (int id) =>
notes.FirstOrDefault(n => n.Id == id) is { } note
? Results.Ok(note)
: Results.NotFound());
api.MapDelete("/{id:int}", (int id) =>
{
var idx = notes.FindIndex(n => n.Id == id);
if (idx < 0) return Results.NotFound();
notes.RemoveAt(idx);
return Results.NoContent();
});
app.Run();
public partial class Program { }
Разбор
| Фрагмент | Смысл |
|---|---|
MapGroup("/api/notes") | Все маршруты ниже с префиксом /api/notes. |
WithTags("Notes") | Группа в Swagger UI. |
MapGet("/", ...) | Полный путь: GET /api/notes/. |
{id:int} | Параметр маршрута только целое число. |
ValidationProblem | Ответ 400 с полем errors по стандарту. |
Results.Created(...) | 201 + тело + заголовок Location. |
Results.NoContent() | 204 без тела (успешное удаление). |
public partial class Program | Для интеграционных тестов. |
dotnet run → /swagger.
TypedResults (явные типы ответов)
OpenAPI точнее, если указать типы:
api.MapGet("/{id:int}", (int id) =>
{
var note = notes.FirstOrDefault(n => n.Id == id);
return note is null ? TypedResults.NotFound() : TypedResults.Ok(note);
})
.Produces<Note>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
.Produces<T> — метаданные для генератора схемы.
Внедрение зависимостей
builder.Services.AddSingleton<INoteRepository, InMemoryNoteRepository>();
api.MapGet("/", (INoteRepository repo) => repo.GetAll());
Параметры handler резолвятся из DI так же, как параметры конструктора контроллера.
Endpoint filters
api.MapPost("/", Handler)
.AddEndpointFilter(async (context, next) =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILoggerFactory>()
.CreateLogger("Notes");
logger.LogInformation("POST /api/notes");
return await next(context);
});
Авторизация на группу:
var api = app.MapGroup("/api/notes").RequireAuthorization();
См. 4515.
OpenAPI — Swashbuckle и встроенный AddOpenApi
Swashbuckle (.NET 8 и привычный Swagger UI)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(/* ... */);
Встроенный OpenAPI (.NET 9+)
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwaggerUI(options =>
options.SwaggerEndpoint("/openapi/v1.json", "v1"));
}
Выбор зависит от версии SDK в проекте.
Аннотации и XML-комментарии
api.MapGet("/{id:int}", GetById)
.WithName("GetNoteById")
.WithSummary("Получить заметку по Id")
.WithDescription("Возвращает 404, если заметка не найдена");
В .csproj:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
builder.Services.AddSwaggerGen(o =>
{
var xml = Path.Combine(AppContext.BaseDirectory, "MinimalNotes.xml");
if (File.Exists(xml)) o.IncludeXmlComments(xml);
});
Версионирование API (обзор)
var v1 = app.MapGroup("/api/v1").WithTags("v1");
var v2 = app.MapGroup("/api/v2").WithTags("v2");
В Swagger — отдельные SwaggerDoc("v1", …) и SwaggerDoc("v2", …).
Валидация
У Minimal API нет автоматического ModelState, как у [ApiController]. Варианты:
- Ручная проверка →
Results.ValidationProblem(как в примере). - FluentValidation в endpoint filter.
- Data Annotations + пакеты вроде MiniValidation.
Для форм с десятком полей иногда проще контроллер с [FromBody] и [ApiController].
Частые ошибки
| Симптом | Причина |
|---|---|
| Swagger пустой | Нет AddEndpointsApiExplorer() |
| 415 | POST без Content-Type: application/json |
| Схема «object» | Handler возвращает object вместо Note / TypedResults |
| Дубли маршрутов | Два MapGet на один шаблон |
404 при {id} | Передан не int — constraint {id:int} отсекает |
Что попробовать
WithOpenApi()на маршруте (если доступно в вашей версии SDK).- Сгенерировать клиент: Kiota или NSwag по
/openapi/v1.json. - Покрыть API интеграционными тестами.
Дальше
- Web API — первая программа · ASP.NET — обзор
- Интеграционные тесты · Identity и JWT
- Справочник ASP.NET
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C# как язык платформы .NET - устройство проекта, роль `.cs`-файлов и базовые принципы организации кода. C# — это современный, типизированный язык программирования общего назначения, разработанный корпорацией Microsoft. Справочник-шпаргалка по конфигурациям в C — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Набор советов, правил, принципов и обычаев в разработке на этом языке. Кавычки, точки, запятые, скобки и прочие знаки препинания. Ключевые слова C# - назначение базовых конструкций языка и примеры их применения в типичном коде. Набор функций, которые включены в стандартную библиотеку языка. Пространства имён в C# - организация модулей, `file-scoped namespace` и поддержание чистой структуры кода. манипулировать данными (арифметические, логические, сравнительные операторы). Самый базовый способ ветвления — оператор if. Он проверяет условие и, если оно истинно (true), выполняет блок кода. Обработка исключений в C# - типы исключений, `try/catch/finally` и практики надежного кода. Платформо-зависимые исключения — например, PlatformNotSupportedException используется в кроссплатформенных API, когда функция недоступна на текущей ОС.C# - язык программирования платформы .NET
Что требуется знать перед началом изучения языка программирования C#
Справочник по конфигурациям в C#
Рекомендации по разработке на C#
Синтаксис и пунктуация в C#
Ключевые слова языка C#
Встроенные функции и методы C#
Пространства имён в C#
Управляющие конструкции и логические операторы
Условные выражения и ветвления
Обработка исключений в C#
Иерархия классов исключений в C#