Какие способы настройки маршрутизации в Minimal APIs ты знаешь?

Ответ

В 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, а у тебя idint. И всё, пиздец, исключение. Привязывай сразу.

// Только цифры, иначе 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 — твой лучший друг для порядка. А фильтры — это магия, которая делает код чище. Всё остальное — от лукавого.