# ASP.NET Core Minimal APIs у 2026: Архітектура, Продуктивність та Питання на Співбесідах > Повний посібник з Minimal APIs в ASP.NET Core - архітектура, групи маршрутів, фільтри ендпоінтів, Native AOT та ключові питання на співбесідах для .NET розробників. - Published: 2026-07-24 - Updated: 2026-07-24 - Author: SharpSkill - Reading time: 5 min --- ASP.NET Core Minimal APIs усувають церемоніальність традиційних MVC контролерів, пропонуючи спрощений підхід до створення HTTP ендпоінтів зі значно меншою кількістю шаблонного коду. Представлені в .NET 6 та вдосконалені протягом .NET 8, 9 і поточного .NET 10, Minimal APIs досягли рівня production-ready рішення для мікросервісів, serverless функцій та легковагових веб-сервісів. > **Minimal APIs vs Контролери** > > Minimal APIs використовують обробники маршрутів верхнього рівня, визначені безпосередньо в Program.cs, тоді як MVC контролери вимагають визначення класів, атрибутів та конвенційної маршрутизації. Для простих CRUD операцій або мікросервісів з менш ніж 20 ендпоінтами Minimal APIs зазвичай зменшують код на 40-60%. ## Архітектура Minimal API та Конвеєр Запитів Конвеєр запитів ASP.NET Core обробляє HTTP запити через компоненти middleware перед досягненням обробників ендпоінтів. Minimal APIs безшовно інтегруються з цим конвеєром, водночас пропонуючи більш декларативний синтаксис для визначення маршрутів. ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); // Реєстрація сервісів для dependency injection builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddScoped(); var app = builder.Build(); // Конфігурація конвеєра middleware app.UseExceptionHandler("/error"); app.UseHttpsRedirection(); app.UseAuthorization(); // Визначення ендпоінтів Minimal API app.MapGet("/products", async (IProductRepository repo) => Results.Ok(await repo.GetAllAsync())); app.MapGet("/products/{id:int}", async (int id, IProductRepository repo) => await repo.GetByIdAsync(id) is Product product ? Results.Ok(product) : Results.NotFound()); app.Run(); ``` Цей патерн централізує визначення маршрутів при збереженні повного доступу до контейнера dependency injection. Параметр `IProductRepository` демонструє ін'єкцію без конструктора безпосередньо в делегати обробників. ## Групи Маршрутів та Організація Ендпоінтів Зі зростанням додатків організація ендпоінтів стає критично важливою. Групи маршрутів, представлені в .NET 7, забезпечують простори імен та спільну конфігурацію без відмови від мінімального підходу. ```csharp // ProductEndpoints.cs public static class ProductEndpoints { public static void MapProductEndpoints(this WebApplication app) { var group = app.MapGroup("/api/products") .WithTags("Products") .RequireAuthorization(); group.MapGet("/", GetAllProducts); group.MapGet("/{id:int}", GetProductById); group.MapPost("/", CreateProduct) .Accepts("application/json") .Produces(StatusCodes.Status201Created); group.MapPut("/{id:int}", UpdateProduct); group.MapDelete("/{id:int}", DeleteProduct) .RequireAuthorization("AdminOnly"); } private static async Task GetAllProducts( IProductRepository repo, CancellationToken ct) { var products = await repo.GetAllAsync(ct); return Results.Ok(products); } private static async Task GetProductById( int id, IProductRepository repo, CancellationToken ct) { var product = await repo.GetByIdAsync(id, ct); return product is not null ? Results.Ok(product) : Results.NotFound(); } private static async Task CreateProduct( CreateProductRequest request, IProductRepository repo, IValidator validator, CancellationToken ct) { var validation = await validator.ValidateAsync(request, ct); if (!validation.IsValid) return Results.ValidationProblem(validation.ToDictionary()); var product = await repo.CreateAsync(request.ToProduct(), ct); return Results.Created($"/api/products/{product.Id}", product); } } ``` Виклик `app.MapProductEndpoints()` у Program.cs реєструє всі маршрути продуктів зі спільними вимогами авторизації та метаданими OpenAPI. Ця структура добре масштабується для додатків із сотнями ендпоінтів. ## Прив'язка Параметрів та Валідація Minimal APIs підтримують декілька джерел прив'язки: параметри маршрутів, query strings, заголовки, тіла запитів та сервіси з DI. Розуміння пріоритету прив'язки дозволяє уникнути типових пасток на співбесідах. ```csharp // Program.cs - Приклади прив'язки параметрів app.MapGet("/search", ( [FromQuery] string? query, // Явний query string [FromQuery] int page = 1, // Значення за замовчуванням [FromQuery] int pageSize = 20, // Значення за замовчуванням [FromHeader(Name = "X-Correlation-Id")] string? correlationId, ILogger logger) => { logger.LogInformation("Запит пошуку: {Query}, Сторінка: {Page}, CorrelationId: {CorrelationId}", query, page, correlationId); return Results.Ok(new { query, page, pageSize, correlationId }); }); // Складна прив'язка моделі з валідацією app.MapPost("/orders", async ( [FromBody] CreateOrderRequest request, [FromServices] IValidator validator, [FromServices] IOrderService orderService, HttpContext context, CancellationToken ct) => { var validationResult = await validator.ValidateAsync(request, ct); if (!validationResult.IsValid) { return Results.ValidationProblem( validationResult.Errors .GroupBy(e => e.PropertyName) .ToDictionary( g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray())); } var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); var order = await orderService.CreateOrderAsync(request, userId!, ct); return Results.Created($"/orders/{order.Id}", order); }); ``` Атрибут `[FromBody]` є опціональним для складних типів, але покращує читабельність. FluentValidation природно інтегрується через dependency injection, зберігаючи логіку валідації окремо від обробників ендпоінтів. > **Пріоритет Джерел Прив'язки** > > Коли атрибут не вказано, Minimal APIs виводять джерела прив'язки: спочатку параметри маршрутів, потім query strings для простих типів та тіло запиту для складних типів. Явні атрибути як [FromQuery] або [FromBody] перевизначають цю поведінку. ## Оптимізація Продуктивності з Native AOT .NET 8 представив підтримку компіляції Native AOT (Ahead-of-Time) для Minimal APIs, створюючи автономні виконувані файли з часом запуску менше мілісекунди. Ця можливість робить Minimal APIs ідеальним вибором для serverless розгортань, де затримка холодного старту має значення. ```csharp // Program.cs - Конфігурація сумісна з AOT var builder = WebApplication.CreateSlimBuilder(args); // AOT-дружня серіалізація JSON builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default); }); var app = builder.Build(); app.MapGet("/health", () => Results.Ok(new HealthResponse("Healthy", DateTime.UtcNow))); app.Run(); // Контекст серіалізатора JSON згенерований з джерельного коду [JsonSerializable(typeof(HealthResponse))] [JsonSerializable(typeof(Product))] [JsonSerializable(typeof(List))] internal partial class AppJsonContext : JsonSerializerContext { } public record HealthResponse(string Status, DateTime CheckedAt); ``` Метод `CreateSlimBuilder` виключає непотрібні функції фреймворку, тоді як згенерований з джерельного коду `JsonSerializerContext` усуває рефлексію під час виконання для серіалізації JSON. Опубліковані AOT бінарні файли для простих API зазвичай мають розмір 10-15 МБ порівняно з 80+ МБ для стандартних автономних розгортань. ## Типізовані Результати та Метадані Відповідей .NET 7 представив `TypedResults` для верифікації типів відповідей під час компіляції, покращуючи точність документації OpenAPI та виявляючи невідповідності типів під час розробки. ```csharp // Строго типізовані результати з метаданими OpenAPI app.MapGet("/products/{id:int}", async Task, NotFound, ProblemHttpResult>> ( int id, IProductRepository repo, CancellationToken ct) => { try { var product = await repo.GetByIdAsync(id, ct); return product is not null ? TypedResults.Ok(product) : TypedResults.NotFound(); } catch (Exception ex) { return TypedResults.Problem( detail: "Під час отримання продукту сталася помилка", statusCode: StatusCodes.Status500InternalServerError); } }) .WithName("GetProductById") .WithOpenApi(operation => { operation.Summary = "Отримує продукт за ID"; operation.Description = "Повертає деталі продукту або 404 якщо не знайдено"; return operation; }); ``` Об'єднаний тип `Results` декларує всі можливі типи відповідей, які генератори Swagger/OpenAPI використовують для створення точної документації. Цей патерн особливо цінний при підготовці до питань на співбесідах щодо проектування API. ## Фільтри Ендпоінтів для Наскрізних Завдань Фільтри ендпоінтів забезпечують функціональність подібну до middleware, обмежену конкретними ендпоінтами або групами, обробляючи такі завдання як логування, кешування та трансформація запитів. ```csharp // ValidationFilter.cs public class ValidationFilter : IEndpointFilter where T : class { public async ValueTask InvokeAsync( EndpointFilterInvocationContext context, EndpointFilterDelegate next) { var validator = context.HttpContext .RequestServices .GetService>(); if (validator is null) return await next(context); var argument = context.Arguments .OfType() .FirstOrDefault(); if (argument is null) return await next(context); var validationResult = await validator.ValidateAsync(argument); if (!validationResult.IsValid) { return Results.ValidationProblem( validationResult.Errors .GroupBy(e => e.PropertyName) .ToDictionary( g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray())); } return await next(context); } } // Використання в Program.cs app.MapPost("/products", CreateProduct) .AddEndpointFilter>(); // Глобальна реєстрація фільтру через групу маршрутів var api = app.MapGroup("/api") .AddEndpointFilter(async (context, next) => { var logger = context.HttpContext .RequestServices .GetRequiredService>(); var stopwatch = Stopwatch.StartNew(); var result = await next(context); stopwatch.Stop(); logger.LogInformation( "Ендпоінт {Method} {Path} завершено за {ElapsedMs}мс", context.HttpContext.Request.Method, context.HttpContext.Request.Path, stopwatch.ElapsedMilliseconds); return result; }); ``` Фільтри виконуються в порядку реєстрації, де найвнутрішніший фільтр знаходиться найближче до обробника ендпоінту. Ця архітектура забезпечує чисте розділення логіки валідації, логування та авторизації. ## Патерни Автентифікації та Авторизації Minimal APIs підтримують ті ж механізми автентифікації та авторизації, що й MVC контролери, з більш декларативним синтаксисом конфігурації. ```csharp // Program.cs - Налаштування JWT автентифікації builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)) }; }); builder.Services.AddAuthorizationBuilder() .AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")) .AddPolicy("PremiumUser", policy => policy.RequireClaim("subscription", "premium", "enterprise")); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); // Захищені ендпоінти app.MapGet("/admin/users", async (IUserService userService) => Results.Ok(await userService.GetAllUsersAsync())) .RequireAuthorization("AdminOnly"); app.MapGet("/profile", async (ClaimsPrincipal user, IUserService userService) => { var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); var profile = await userService.GetProfileAsync(userId!); return Results.Ok(profile); }) .RequireAuthorization(); // Анонімний ендпоінт у захищеній групі var protectedGroup = app.MapGroup("/api/secure") .RequireAuthorization(); protectedGroup.MapGet("/public-info", () => Results.Ok("Це публічне")) .AllowAnonymous(); ``` Метод розширення `RequireAuthorization` приймає назви політик або може викликатися без аргументів для вимоги будь-якого автентифікованого користувача. Розуміння цих патернів є необхідним для питань на співбесідах про автентифікацію та авторизацію. ## Питання на Співбесідах: Типові Патерни Технічні співбесіди часто досліджують відмінності між Minimal APIs та традиційними контролерами. Таблиця нижче підсумовує ключові відмінності: | Аспект | Minimal APIs | MVC Контролери | |--------|--------------|----------------| | Шаблонний код | Низький - прямі делегати обробників | Вищий - клас + метод + атрибути | | Маршрутизація | Вбудована з `MapGet`, `MapPost` | На основі атрибутів або конвенцій | | Прив'язка моделі | Автоматична з опціональними атрибутами | Конвенція + атрибути | | Фільтри | Фільтри ендпоінтів | Фільтри дій + middleware | | Підтримка AOT | Повна з .NET 8 | Обмежена, базована на рефлексії | | Тестованість | На основі функцій, легкі unit тести | Вимагає інстанціювання контролера | | Найкраще для | Мікросервіси, прості API | Великі додатки, складні workflow | > **Порада для Співбесіди** > > Коли запитують "Коли б ви обрали контролери замість Minimal APIs?", згадайте складні додатки, що вимагають фільтрів дій, налаштування прив'язки моделей або існуючі кодові бази з усталеними патернами MVC. Minimal APIs відмінно підходять для нових мікросервісів та serverless функцій. ## Тестування Ендпоінтів Minimal API Інтеграційне тестування з `WebApplicationFactory` забезпечує реалістичну верифікацію ендпоінтів без розгортання додатку. ```csharp // ProductEndpointsTests.cs public class ProductEndpointsTests : IClassFixture> { private readonly HttpClient _client; private readonly WebApplicationFactory _factory; public ProductEndpointsTests(WebApplicationFactory factory) { _factory = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(services => { // Заміна реального репозиторію на mock services.RemoveAll(); services.AddScoped(); }); }); _client = _factory.CreateClient(); } [Fact] public async Task GetProducts_ReturnsOkWithProductList() { // Act var response = await _client.GetAsync("/api/products"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var products = await response.Content .ReadFromJsonAsync>(); products.Should().NotBeNull(); products.Should().HaveCountGreaterThan(0); } [Fact] public async Task GetProductById_WithInvalidId_ReturnsNotFound() { // Act var response = await _client.GetAsync("/api/products/99999"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); } [Fact] public async Task CreateProduct_WithValidRequest_ReturnsCreated() { // Arrange var request = new CreateProductRequest("Тестовий Продукт", 29.99m, "Тестовий опис"); // Act var response = await _client.PostAsJsonAsync("/api/products", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); response.Headers.Location.Should().NotBeNull(); } } ``` Для глибшого вивчення патернів чистої архітектури в .NET додатках сервісний шар має тестуватися незалежно unit тестами, тоді як інтеграційні тести верифікують повний конвеєр запитів. ## Висновок Minimal APIs представляють сучасний підхід до створення HTTP сервісів в ASP.NET Core: - Групи маршрутів та організація ендпоінтів масштабуються від простих мікросервісів до складних додатків - Фільтри ендпоінтів забезпечують чисте розділення наскрізних завдань як валідація та логування - TypedResults забезпечують верифікацію типів відповідей під час компіляції та точну документацію OpenAPI - Компіляція Native AOT забезпечує час запуску менше мілісекунди для serverless розгортань - Інтеграційне тестування з WebApplicationFactory забезпечує реалістичну верифікацію ендпоінтів При підготовці до співбесід зосередьтеся на поясненні того, коли Minimal APIs є доречними порівняно з традиційними контролерами, та продемонструйте розуміння конвеєра запитів, dependency injection та патернів автентифікації. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/dotnet/aspnet-core-minimal-apis-architecture-performance-interview