Start Debugging

Minimal APIs vs контроллеры в ASP.NET Core 11: что выбрать в 2026 году?

В ASP.NET Core 11 по умолчанию выбирайте minimal APIs. Контроллеры берите только тогда, когда нужны возможности MVC, которые minimal APIs всё ещё не покрывают: маршрутизация по соглашениям для множества действий, фильтры в стиле MVC или Razor-представления.

Если вы начинаете новый HTTP-сервис на ASP.NET Core 11 и выбираете между minimal APIs и контроллерами, выбирайте minimal APIs. Причины 2022 года продолжать использовать контроллеры (нет фильтров, нет валидации, слабый OpenAPI, нет групп маршрутов, нет Native AOT) исчезли. Начиная с .NET 11 у minimal APIs есть фильтры эндпоинтов, группы маршрутов, встроенный документ OpenAPI через Microsoft.AspNetCore.OpenApi, валидация параметров через [Validate] в Microsoft.AspNetCore.Http.Validation, TypedResults и полная поддержка Native AOT. Контроллеры остаются правильным инструментом в двух конкретных случаях: вам нужны Razor-представления (MVC или Razor Pages) в том же проекте, либо вы сопровождаете большую существующую кодовую базу с сотнями действий, помеченных [Route], которые сегодня работают.

Все примеры кода в этой статье ориентированы на <TargetFramework>net11.0</TargetFramework> с <LangVersion>14.0</LangVersion> и используют пакеты ASP.NET Core 11, поставляемые в .NET 11 GA SDK.

Матрица возможностей

ВозможностьMinimal APIs (ASP.NET Core 11)Controllers (ASP.NET Core 11)
МаршрутизацияEndpoint routing, MapGet/MapPost, группы маршрутовEndpoint routing, по атрибутам или по соглашениям
Фильтры на эндпоинтIEndpointFilter, AddEndpointFilter<T>IActionFilter, IAsyncActionFilter
Вывод источника при привязкеПравила привязки по параметрам, [FromBody] необязателен[FromBody], [FromQuery], [FromForm] и др.
Валидация[Validate] в Microsoft.AspNetCore.Http.Validation (ASP.NET Core 11)ModelState с DataAnnotations, ApiController
Хелперы результатовTypedResults, ResultsIActionResult, ActionResult<T>, Problem()
OpenAPI / SwaggerMicrosoft.AspNetCore.OpenApi 11, без лишней обвязкиMicrosoft.AspNetCore.OpenApi 11 + соглашения
Native AOTПолная поддержка (PublishAot=true работает)Ограниченная; AddControllers всё ещё выдаёт предупреждения trim
Razor-представления / Razor PagesНе поддерживается (нет конвейера рендеринга представлений)Поддерживается (AddControllersWithViews, Razor Pages)
Antiforgery (POST формы с cookie)app.UseAntiforgery() + [FromForm]Встроено в MVC по умолчанию через [ValidateAntiForgeryToken]
Стандартный шаблонный код на эндпоинт1 лямбда + 1 строка MapX1 класс + 1 метод + атрибуты
Обнаруживаемость в кодовой базе на 200 эндпоинтовФайлы групп / методы расширенияОдин класс контроллера на ресурс
Размер бинарника при AOT-публикацииСамый маленький во фреймворкеБольше; полный конвейер MVC
Пропускная способность (маленький JSON-эндпоинт, в стиле TechEmpower)~3-5% выше, чем у контроллеровБазовая линия

Числа в последней строке взяты из собственных бенчмарков команды .NET для ASP.NET Core 11. Для большинства приложений эту разницу можно считать “на уровне шума”. Причина выбирать minimal APIs не в дельте пропускной способности. Дело в меньшей поверхности и в истории с AOT.

Когда minimal APIs - правильный выбор

Используйте minimal APIs по умолчанию для любого нового HTTP-сервиса на ASP.NET Core 11. Конкретные случаи, где они блистают:

Маленький, но реалистичный эндпоинт:

// .NET 11, C# 14
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddScoped<IInvoiceStore, SqlInvoiceStore>();

var app = builder.Build();
app.MapOpenApi();

var invoices = app.MapGroup("/invoices")
    .WithTags("Invoices")
    .RequireAuthorization();

invoices.MapGet("/{id:int}", async (
    int id,
    IInvoiceStore store,
    CancellationToken ct) =>
{
    var invoice = await store.FindAsync(id, ct);
    return invoice is null
        ? Results.NotFound()
        : TypedResults.Ok(invoice);
});

invoices.MapPost("/", async Task<Results<Created<Invoice>, ValidationProblem>> (
    [Validate] CreateInvoice request,
    IInvoiceStore store,
    CancellationToken ct) =>
{
    var created = await store.CreateAsync(request, ct);
    return TypedResults.Created($"/invoices/{created.Id}", created);
});

app.Run();

CreateSlimBuilder подключает минимальный набор сервисов, что и делает Native AOT жизнеспособным. MapGroup плюс RequireAuthorization - это история о группах маршрутов и общих политиках, которой не было до .NET 7. [Validate] - атрибут ASP.NET Core 11, активирующий Microsoft.AspNetCore.Http.Validation, валидатор на основе генератора исходного кода, который заменяет конвейер DataAnnotations, ранее доступный только в контроллерах. Возвращаемый тип Results<TOk, TErr> - это то, что делает документ OpenAPI точным без какой-либо дополнительной аннотации Produces.

Когда контроллеры - правильный выбор

Беритесь за контроллеры, когда верно одно из следующих утверждений:

Сопоставимый контроллер для эндпоинтов счетов:

// .NET 11, C# 14
[ApiController]
[Route("invoices")]
[Authorize]
public class InvoicesController(IInvoiceStore store) : ControllerBase
{
    [HttpGet("{id:int}")]
    [ProducesResponseType(typeof(Invoice), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<Invoice>> Get(int id, CancellationToken ct)
    {
        var invoice = await store.FindAsync(id, ct);
        return invoice is null ? NotFound() : Ok(invoice);
    }

    [HttpPost]
    [ProducesResponseType(typeof(Invoice), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<Invoice>> Create(
        CreateInvoice request,
        CancellationToken ct)
    {
        var created = await store.CreateAsync(request, ct);
        return CreatedAtAction(nameof(Get), new { id = created.Id }, created);
    }
}

Два эндпоинта, в два раза больше шаблонного кода, никакого функционального выигрыша. Это и есть тот паттерн, который делает minimal APIs выбором по умолчанию в 2026 году: в новом коде вы пишете меньше, выражая то же самое.

Бенчмарк

Разрыв в производительности между двумя стилями в ASP.NET Core 11 небольшой, но устойчивый на маленьких JSON-эндпоинтах. Официальные бенчмарки команды .NET (https://github.com/aspnet/Benchmarks, результаты ASP.NET Core 11 GA, опубликованные в ноябре 2025) показывают:

СценарийMinimal APIs (RPS)Controllers (RPS)Дельта
GET /json (один объект, 200 байт)1 160 0001 120 000+3,6%
POST /json (echo, тело 1 КБ)590 000565 000+4,4%
GET /plaintext (TechEmpower)7 250 0007 250 0000%
Холодный старт (AOT, dotnet publish -aot)70 мс280 мс-75%

Методология, кратко из опубликованных прогонов: ASP.NET Core 11 GA, Linux x64, машины Citrine, wrk нагружает Kestrel 256 одновременными соединениями, 30-секундные прогоны, усреднённые по пяти выборкам. Строка холодного старта публикуется из той же лаборатории с помощью time на AOT-бинарнике при первом запуске. plaintext одинаков, потому что стоимость конвейера запроса определяется парсингом Kestrel, а не формой эндпоинта.

Честное прочтение этих чисел: если вы упираетесь в CPU на горячем JSON-эндпоинте, переходом с контроллеров на minimal APIs можно выиграть 3-5%. Реальные приложения проводят время в EF Core или в исходящих HTTP-запросах, а не в диспетчере эндпоинтов. Строка холодного старта, однако, реальна: если вы разворачиваетесь в AWS Lambda или Azure Functions, дельта AOT - это разница между сервисом, который прогревается менее чем за секунду, и тем, который не прогревается. Сопутствующий разбор о сокращении холодного старта Lambda на .NET 11 проводит по AOT-публикации, которая и даёт эти числа.

Подводный камень, который выбирает за вас

Три ограничения решают вопрос без участия предпочтений:

Чего minimal APIs всё ещё не делают

Кое-что, что контроллеры умеют, а minimal APIs в ASP.NET Core 11 всё ещё не повторяют:

Если что-то из этих трёх пунктов несущее в вашем дизайне, баланс затрат и выгод снова склоняется к контроллерам. Для 95% новых сервисов ASP.NET Core 11 - нет.

Выбор, ещё раз

По умолчанию используйте minimal APIs в ASP.NET Core 11. Это способ построить JSON-HTTP-сервис в .NET 11 с меньшим количеством шаблонного кода, готовый к AOT и чистый по OpenAPI. Исторические причины предпочитать контроллеры (фильтры, валидация, группы маршрутов, OpenAPI, соглашения) закрывались одна за другой в .NET 7, 8, 10 и 11. Остаётся небольшой набор возможностей MVC (Razor-представления, model binders, action constraints), которыми всё ещё владеют контроллеры. Если ваш проект в них нуждается, используйте контроллеры для тех частей, где они нужны, и minimal APIs для всего остального. Оба стиля прекрасно сосуществуют в одном WebApplication.

Похожее

Источники

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад