Ответ
В Minimal APIs маршрутизация настраивается декларативно с помощью методов Map* и их перегрузок. Вот основные приёмы.
1. Базовые маршруты с параметрами
app.MapGet("/", () => "Hello World!");
app.MapGet("/users/{id}", (int id) => $"User ID: {id}");
app.MapPost("/users", (User user) => Results.Created($"/users/{user.Id}", user));
2. Ограничения типов и дополнительные параметры в маршруте Параметры можно ограничивать по типу и делать опциональными.
// Строго типизированный параметр
app.MapGet("/products/{id:int}", (int id) => ...);
// Ограничение Guid
app.MapGet("/documents/{docId:guid}", (Guid docId) => ...);
// Опциональный параметр (задаётся через параметр метода с значением по умолчанию)
app.MapGet("/search/{term?}", (string term = "default") => $"Searching for: {term}");
// "Catch-all" параметр
app.MapGet("/files/{*path}", (string path) => $"File path: {path}");
3. Группировка маршрутов с MapGroup
Позволяет задать общий префикс, фильтры и политики для набора эндпоинтов.
var api = app.MapGroup("/api");
api.MapGet("/", () => "API Root");
var users = api.MapGroup("/users");
users.MapGet("/", () => "All users");
users.MapGet("/{id}", (int id) => $"User {id}");
// Группа с предварительной конфигурацией
var admin = app.MapGroup("/admin")
.RequireAuthorization("AdminPolicy")
.WithTags("Admin");
admin.MapGet("/dashboard", () => "Admin Dashboard");
4. Фильтрация маршрутов (Route Filters)
Позволяют выполнять код до (Before) и после (After) обработчика эндпоинта.
app.MapGet("/secure", () => "Secret data")
.AddEndpointFilter(async (context, next) =>
{
// Логика перед вызовом handler
var result = await next(context);
// Логика после вызова handler
return result;
});
5. Кастомные привязки параметров
Можно реализовать интерфейс BindAsync в своём типе, чтобы Minimal API знал, как создать его из контекста запроса.
public record CustomParam(string Value)
{
public static ValueTask<CustomParam?> BindAsync(HttpContext context) => ...
}
app.MapGet("/custom", (CustomParam param) => param.Value);
6. Интеграция с OpenAPI (Swagger)
Методы WithName, WithTags, WithDescription и WithOpenApi для документирования.
app.MapGet("/products/{id}", (int id) => ...)
.WithName("GetProductById")
.WithTags("Products")
.WithSummary("Returns a product by its ID")
.Produces<Product>(200)
.Produces(404);
Совет: Используйте MapGroup для организации кода. Для сложной логики валидации и обработки выносите код в отдельные методы или классы, а в лямбде эндпоинта оставляйте только вызов.
Ответ 18+ 🔞
Слушай, вот тебе реальная картина по маршрутизации в Minimal API, без этой официозной хуйни.
1. Базовые штуки — тут всё просто
Ну, создал app и начинаешь накидывать маршруты, как блинчики на сковородку. MapGet, MapPost — интуитивно понятно, чё тут объяснять.
app.MapGet("/", () => "Привет, мир!"); // Корень, классика
app.MapGet("/users/{id}", (int id) => $"Юзер с ID: {id}"); // Параметр прямо в пути
app.MapPost("/users", (User user) => Results.Created($"/users/{user.Id}", user)); // Принял объект — отдал ответ
Главное — не выёбывайся на этом этапе.
2. Ограничения в маршрутах — чтобы не лезло всякое говно
А то придёт запрос на /products/abc, а у тебя id — int. И всё, пиздец, исключение. Привязывай сразу.
// Только цифры, иначе 404
app.MapGet("/products/{id:int}", (int id) => ...);
// Ждём конкретно Guid, не какую-то хуйню
app.MapGet("/documents/{docId:guid}", (Guid docId) => ...);
// Параметр может быть, а может и не быть. Значение по умолчанию — спасёт.
app.MapGet("/search/{term?}", (string term = "default") => $"Ищем: {term}");
// "Catch-all" — словит всё, что угодно, типа /files/folder/subfolder/file.txt
app.MapGet("/files/{*path}", (string path) => $"Путь к файлу: {path}");
Вот так отсекаешь мусорные запросы ещё до входа в твой метод.
3. Группировка (MapGroup) — чтобы не повторяться как попугай
Представь, у тебя куча эндпоинтов начинаются с /api. Зачем каждый раз это писать? Создал группу — и всё.
var api = app.MapGroup("/api");
api.MapGet("/", () => "Это корень API");
var users = api.MapGroup("/users");
users.MapGet("/", () => "Все юзеры");
users.MapGet("/{id}", (int id) => $"Юзер {id}");
// А можно и политики навесить на всю группу сразу
var admin = app.MapGroup("/admin")
.RequireAuthorization("AdminPolicy") // Только для своих
.WithTags("Admin"); // Для красоты в Swagger
admin.MapGet("/dashboard", () => "Админка, вход воспрещён!");
Организация — наше всё. Иначе потом сам в своём коде не разберёшься.
4. Фильтры эндпоинтов — твой шанс влезть до и после Хочешь логировать, проверять права, или ещё какую дичь делать? Фильтры рулят.
app.MapGet("/secure", () => "Секретные данные")
.AddEndpointFilter(async (context, next) =>
{
// Тут ты перед вызовом основного кода. Можешь, например, проверить заголовок.
// Если что-то не так — верни Results.Unauthorized() и не вызывай next.
var result = await next(context); // А вот тут уже отработал твой handler.
// Тут ты после. Можешь, например, к результату что-то прикрутить.
return result;
});
Мощнейшая штука, если использовать с умом.
5. Своя привязка параметров — для самых хитрых
Допустим, тебе нужно из куков, хедеров и тела запроса сразу собрать один объект. Реализуй BindAsync в своём классе или рекорде.
public record MyCustomParam(string ValueFromHeader, int ValueFromQuery)
{
public static ValueTask<MyCustomParam?> BindAsync(HttpContext context)
{
// Достаёшь из контекста что нужно, собираешь объект.
var param = new MyCustomParam(
context.Request.Headers["X-My-Header"],
int.Parse(context.Request.Query["id"])
);
return ValueTask.FromResult<MyCustomParam?>(param);
}
}
// И используешь как обычный параметр
app.MapGet("/custom", (MyCustomParam param) => $"Header: {param.ValueFromHeader}");
Когда стандартной привязки не хватает — это твой выход.
6. OpenAPI (Swagger) — чтобы фронтендеры не доставали А то придут и спросят: "А как этот метод называется? А что он возвращает?". Подскажи им.
app.MapGet("/products/{id}", (int id) => ...)
.WithName("GetProductById") // Уникальное имя для ссылок
.WithTags("Products") // Группировка в UI
.WithSummary("Возвращает товар по айдишнику") // Коротко о главном
.Produces<Product>(StatusCodes.Status200OK) // Говорим, что в случае успеха вернётся Product
.Produces(StatusCodes.Status404NotFound); // А если не найдётся — 404
Потрать пять минут, сэкономишь час на объяснениях.
Итог: Не городи всю логику в лямбде эндпоинта. Выноси в отдельные методы или сервисы. MapGroup — твой лучший друг для порядка. А фильтры — это магия, которая делает код чище. Всё остальное — от лукавого.