Топ-25 питань на співбесіді з ASP.NET Core: Middleware, DI та Minimal APIs

Підготовка до інтерв'ю .NET: питання про middleware pipeline, dependency injection lifecycle, minimal APIs організацію та розширені концепції ASP.NET Core.

Діаграма конвеєра обробки запитів ASP.NET Core з middleware компонентами

Співбесіди з ASP.NET Core часто зосереджуються на трьох ключових областях: розумінні конвеєра middleware, управлінні залежностями через вбудований DI-контейнер та здатності ефективно працювати з Minimal APIs. Ці теми є фундаментальними для сучасної розробки на .NET і регулярно перевіряються на позиціях від junior до senior рівня. Незалежно від того, чи готуєтесь ви до першого інтерв'ю, чи хочете освіжити знання перед складною технічною розмовою, розуміння цих концепцій на глибокому рівні є критичним для успіху.

Що перевіряють роботодавці

Більшість технічних інтерв'юерів шукають не просто знання синтаксису, а розуміння архітектурних рішень та їхніх наслідків. Питання про middleware перевіряють ваше розуміння порядку виконання запитів, DI-питання оцінюють знання lifecycle та memory management, а Minimal APIs показують вашу здатність писати чистий, підтримуваний код у сучасному стилі ASP.NET Core.

Питання про Middleware Pipeline в ASP.NET Core

1. Поясніть, що таке middleware в ASP.NET Core і чому важливий порядок його реєстрації

Middleware — це компоненти, які формують конвеєр обробки HTTP-запитів. Кожен middleware може обробляти запит до виклику наступного компонента, виконувати логіку після нього або повністю зупинити конвеєр (short-circuit). Порядок реєстрації визначає послідовність виконання: перший зареєстрований middleware буде першим, який отримає запит, і останнім, який обробить відповідь.

Program.cs — Middleware order matterscsharp
var app = builder.Build();

app.UseExceptionHandler("/error");   // 1. Outermost: catches all exceptions
app.UseHsts();                        // 2. HTTP Strict Transport Security
app.UseHttpsRedirection();            // 3. Redirect HTTP to HTTPS
app.UseStaticFiles();                 // 4. Serve static files (short-circuits if found)
app.UseRouting();                     // 5. Match request to endpoint
app.UseAuthentication();              // 6. Identify the user
app.UseAuthorization();               // 7. Check permissions
app.MapControllers();                 // 8. Execute the matched endpoint

app.Run();

Неправильний порядок може призвести до проблем безпеки: якщо розмістити UseAuthorization() перед UseAuthentication(), авторизація не матиме інформації про користувача.

2. Як створити власний middleware і що таке конвенція InvokeAsync?

Custom middleware створюється як клас з конструктором, який приймає RequestDelegate (посилання на наступний компонент), та методом InvokeAsync, який отримує HttpContext. Цей метод повинен викликати _next(context) для передачі управління далі по конвеєру.

RequestTimingMiddleware.cscsharp
public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestTimingMiddleware> _logger;

    public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        
        await _next(context);  // Call the next middleware
        
        stopwatch.Stop();
        _logger.LogInformation(
            "Request {Method} {Path} completed in {Elapsed}ms",
            context.Request.Method,
            context.Request.Path,
            stopwatch.ElapsedMilliseconds);
    }
}

// Registration in Program.cs
app.UseMiddleware<RequestTimingMiddleware>();

Middleware може отримувати залежності через конструктор (singleton lifetime) або як параметри InvokeAsync (scoped/transient lifetime).

3. Що таке terminal middleware і коли він використовується?

Terminal middleware — це компонент, який не викликає _next(context) і завершує конвеєр обробки. Класичні приклади: app.Run(), обробка статичних файлів (коли файл знайдено), або custom middleware для health checks. Використовується, коли відповідь повністю сформована і немає потреби в подальшій обробці.

4. У чому різниця між app.Use() і app.Run()?

app.Use() реєструє middleware, який може викликати наступний компонент через делегат next. app.Run() реєструє terminal middleware, який завершує конвеєр і не має доступу до next. app.Run() завжди повинен бути останнім у ланцюжку.

5. Як middleware обробляє винятки і чому UseExceptionHandler має бути першим?

UseExceptionHandler повинен бути першим middleware, щоб обгорнути весь конвеєр в try-catch блок. Коли виникає виняток у будь-якому наступному компоненті, цей middleware перехоплює його і перенаправляє на вказаний error endpoint. Якщо розмістити його пізніше, винятки з попередніх middleware не будуть оброблені.

6. Що таке branch middleware і як використовується MapWhen?

MapWhen створює умовну гілку конвеєра на основі предиката, який перевіряє HttpContext. Якщо умова істинна, запит обробляється окремою гілкою middleware, інакше — йде далі по основному конвеєру. Використовується для створення спеціалізованих обробників для певних типів запитів без впливу на решту програми.

Питання про Dependency Injection

7. Поясніть три типи lifetime в DI: Transient, Scoped і Singleton

Transient створює новий екземпляр кожного разу, коли сервіс запитується. Використовується для легких stateless сервісів без стану. Scoped створює один екземпляр на HTTP-запит (scope). Ідеальний для сервісів, які працюють з БД (DbContext). Singleton створює один екземпляр на весь час життя додатку. Використовується для кешів, конфігурацій, логерів. Вибір неправильного lifetime може призвести до memory leaks або багатопоточних проблем.

8. Що таке captive dependency і як його виявити?

Captive dependency виникає, коли сервіс з довшим lifetime (наприклад, Singleton) захоплює залежність з коротшим lifetime (Scoped або Transient). Це призводить до того, що Scoped сервіс живе довше, ніж його scope, викликаючи несподівану поведінку та потенційні витоки пам'яті.

Program.cs — Captive dependency detectioncsharp
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddSingleton<ICacheService, CacheService>(); // CacheService takes IUserRepository

// This throws at startup in Development when ValidateScopes is enabled:
builder.Host.UseDefaultServiceProvider(options =>
{
    options.ValidateScopes = true;    // Catches captive dependencies
    options.ValidateOnBuild = true;   // Validates all registrations at startup
});
Увага: Captive Dependencies

Ця помилка часто виявляється лише в production, якщо не увімкнено ValidateScopes. У development середовищі ASP.NET Core за замовчуванням вмикає цю перевірку, але явна конфігурація гарантує безпеку.

9. Як реєструвати generic типи в DI контейнері?

ASP.NET Core підтримує реєстрацію відкритих generic типів, що дозволяє одну реєстрацію для всіх закритих варіантів. Використовується паттерн AddTransient(typeof(IRepository<>), typeof(Repository<>)). DI контейнер автоматично резолвить правильну реалізацію на основі запитуваного типу.

10. Що таке keyed services у .NET 8+ і навіщо вони потрібні?

Keyed services дозволяють реєструвати кілька реалізацій одного інтерфейсу з унікальними ключами та вибирати потрібну за допомогою атрибута [FromKeyedServices("key")]. До .NET 8 для цього використовували фабрики або named options паттерни, що було багатослівнішим.

Program.cs — Registering keyed servicescsharp
builder.Services.AddKeyedSingleton<INotificationService, EmailNotification>("email");
builder.Services.AddKeyedSingleton<INotificationService, SmsNotification>("sms");
builder.Services.AddKeyedSingleton<INotificationService, PushNotification>("push");

// Resolving in a controller or service
public class OrderService
{
    public OrderService(
        [FromKeyedServices("email")] INotificationService emailService,
        [FromKeyedServices("sms")] INotificationService smsService)
    {
        // Each parameter gets its specific implementation
    }
}

11. Як працює decorator pattern з DI і навіщо він використовується?

Decorator pattern дозволяє обгортати реалізацію додатковою логікою (кешування, логування, валідація) без зміни оригінального коду. У DI це реалізується через реєстрацію базової реалізації та обгортки, яка приймає внутрішній сервіс через конструктор.

csharp
// Decorating IProductService with caching
builder.Services.AddScoped<ProductService>();
builder.Services.AddScoped<IProductService>(sp =>
{
    var inner = sp.GetRequiredService<ProductService>();
    var cache = sp.GetRequiredService<IMemoryCache>();
    return new CachedProductService(inner, cache);
});

Переваги: дотримання Open/Closed principle, легке додавання cross-cutting concerns, можливість комбінувати кілька декораторів.

12. Як тестувати код, який залежить від DI контейнера?

У unit тестах створюйте mock-об'єкти залежностей та передавайте їх безпосередньо в конструктор. Для integration тестів використовуйте WebApplicationFactory<TProgram>, який створює тестовий DI контейнер, де можна замінити сервіси через ConfigureTestServices(). Для middleware тестів створюйте HttpContext вручну та передавайте fake залежності.

Готовий до співбесід з .NET?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Питання про Minimal APIs

13. Які переваги Minimal APIs порівняно з Controllers?

Minimal APIs зменшують boilerplate код, пришвидшують час запуску (менше рефлексії), краще підходять для мікросервісів та serverless сценаріїв. Вони використовують source generators для parameter binding, що покращує продуктивність. Controllers краще для складних API з багатьма endpoints та потребою в фільтрах, model binding та інших MVC features.

14. Як організувати Minimal API endpoints у великих проектах?

Використовуйте extension методи для групування endpoints за features або доменами. Створюйте static класи з методом MapXxxEndpoints, який реєструє всі пов'язані endpoints через MapGroup(). Це забезпечує вертикальне slice organization та полегшує навігацію в кодовій базі.

Features/Products/ProductEndpoints.cscsharp
public static class ProductEndpoints
{
    public static void MapProductEndpoints(this WebApplication app)
    {
        var group = app.MapGroup("/api/products")
            .WithTags("Products")
            .RequireAuthorization();

        group.MapGet("/", GetAll);
        group.MapGet("/{id:int}", GetById);
        group.MapPost("/", Create);
    }

    private static async Task<IResult> GetAll(
        IProductService productService,    // Auto-resolved from DI
        CancellationToken cancellationToken)
    {
        var products = await productService.GetAllAsync(cancellationToken);
        return TypedResults.Ok(products);
    }

    private static async Task<IResult> GetById(
        int id,
        IProductService productService)
    {
        var product = await productService.GetByIdAsync(id);
        return product is null
            ? TypedResults.NotFound()
            : TypedResults.Ok(product);
    }

    private static async Task<IResult> Create(
        CreateProductRequest request,
        IProductService productService)
    {
        var product = await productService.CreateAsync(request);
        return TypedResults.Created($"/api/products/{product.Id}", product);
    }
}

// Program.cs — Clean registration
app.MapProductEndpoints();

15. Що таке TypedResults і чому він кращий за Results?

TypedResults повертає строго типізовані результати, що дозволяє компілятору перевіряти типи та забезпечує кращий IntelliSense. Це особливо корисно для OpenAPI генерації, оскільки типи відповідей автоматично документуються. Results повертає IResult інтерфейс без інформації про конкретний тип, що ускладнює статичний аналіз.

16. Як працює parameter binding в Minimal APIs?

ASP.NET Core автоматично резолвить параметри з різних джерел: route values, query string, headers, body, DI контейнер. Використовує source generators для генерації binding коду на етапі компіляції, що швидше за runtime рефлексію. Можна явно вказати джерело через атрибути [FromRoute], [FromQuery], [FromBody], [FromServices].

17. Як додати validation до Minimal APIs?

Використовуйте endpoint filters або middleware. Найпопулярніший підхід — інтеграція з FluentValidation через filter, який перехоплює запити, валідує DTO та повертає 400 BadRequest з деталями помилок. Альтернатива — Data Annotations з ручною перевіркою або custom validation middleware.

18. Як додати авторизацію та CORS до групи endpoints?

Використовуйте RequireAuthorization() для додавання policy до всієї групи. Для CORS викликайте RequireCors("PolicyName") на групі. Ці методи застосовують налаштування до всіх endpoints у групі, уникаючи дублювання коду.

Розширені питання для досвідчених розробників

19. Поясніть різницю між IApplicationBuilder.Use() та middleware класами

Use() дозволяє inline middleware через лямбда-вирази, що зручно для простої логіки. Middleware класи забезпечують краще тестування, dependency injection через конструктор та InvokeAsync, та можливість повторного використання. У production коді завжди віддавайте перевагу класам для складної логіки.

20. Як реалізувати background tasks у ASP.NET Core через IHostedService?

IHostedService дозволяє запускати фонові задачі під час старту додатку. Реалізуйте StartAsync для ініціалізації та StopAsync для graceful shutdown. Для періодичних задач використовуйте BackgroundService базовий клас з ExecuteAsync методом та PeriodicTimer. Важливо обробляти CancellationToken для коректної зупинки.

21. Що таке endpoint routing і як він покращив ASP.NET Core?

Endpoint routing розділив процес на два етапи: matching (UseRouting) і execution (UseEndpoints/MapControllers). Це дозволило middleware між цими викликами знати, який endpoint буде виконано, ще до виконання. Використовується для авторизації, CORS, rate limiting на рівні конкретних endpoints.

22. Як працює model binding в ASP.NET Core і які є джерела даних?

Model binding автоматично мапить HTTP-дані на параметри методів. Джерела: route values, query string, form data, request body (JSON/XML), headers, services з DI. Порядок пошуку визначається типом параметра та атрибутами. Complex types біндяться з body, прості типи — з route/query. Custom model binders дозволяють власну логіку.

23. Що таке endpoint filters в Minimal APIs і як вони відрізняються від middleware?

Endpoint filters застосовуються до конкретних endpoints, тоді як middleware до всього конвеєра. Filters мають доступ до endpoint metadata, параметрів та результату виконання. Вони виконуються після routing, але до бізнес-логіки. Використовуються для валідації, логування, кешування на рівні конкретних endpoints.

24. Як оптимізувати продуктивність ASP.NET Core додатків?

Використовуйте асинхронні методи для I/O операцій, response caching та output caching для статичного контенту, connection pooling для БД, HTTP/2 для зменшення latency. Увімкніть response compression, використовуйте CDN для статики, profile додаток з dotnet-trace та dotnet-counters.

25. Як тестувати middleware в unit тестах?

Створюйте екземпляр middleware безпосередньо, передаючи fake RequestDelegate та mock залежності. Створюйте DefaultHttpContext вручну, налаштовуйте необхідні властивості (Request.Method, Path тощо), викликайте InvokeAsync та перевіряйте стан контексту або виклики залежностей.

RequestTimingMiddlewareTests.cscsharp
[Fact]
public async Task Middleware_LogsElapsedTime()
{
    var logger = new FakeLogger<RequestTimingMiddleware>();
    var middleware = new RequestTimingMiddleware(
        next: (ctx) => Task.CompletedTask,  // Terminal delegate
        logger: logger);

    var context = new DefaultHttpContext();
    context.Request.Method = "GET";
    context.Request.Path = "/api/products";

    await middleware.InvokeAsync(context);

    Assert.Single(logger.LogEntries);
    Assert.Contains("completed in", logger.LogEntries[0].Message);
}
Підготовка до інтерв'ю

Найкращий спосіб підготуватися — практика. Створіть pet project з Minimal APIs, реалізуйте власні middleware, експериментуйте з різними DI lifetimes. Читайте вихідний код ASP.NET Core на GitHub — це допоможе зрозуміти архітектурні рішення та best practices від команди Microsoft.

Висновки

Успішне проходження технічного інтерв'ю з ASP.NET Core вимагає глибокого розуміння трьох ключових областей:

  • Middleware pipeline: порядок виконання, створення custom компонентів, обробка винятків, розуміння terminal middleware та branch логіки
  • Dependency Injection: вибір правильного lifetime, уникнення captive dependencies, використання keyed services, реалізація decorator pattern
  • Minimal APIs: організація endpoints у великих проектах, parameter binding, використання TypedResults, додавання validation та авторизації
  • Розширені теми: endpoint routing, background services, model binding, endpoint filters та оптимізація продуктивності

Ці знання не лише допоможуть успішно пройти інтерв'ю, але й зроблять кращим розробником, здатним приймати обґрунтовані архітектурні рішення. Розуміння цих концепцій на глибокому рівні відрізняє senior розробників від junior: перші знають не лише "як", але й "чому" та "коли" використовувати ті чи інші підходи.

Платформа SharpSkill пропонує спеціалізовані модулі підготовки з реальними питаннями, інтерактивними тестами та симуляторами технічних інтерв'ю. Практикуйте відповіді на питання про ASP.NET Core, Entity Framework, design patterns та інші критичні теми для успішної кар'єри .NET розробника.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#asp.net core
#interview
#middleware
#dependency injection
#minimal apis
#dotnet

Поділитися

Пов'язані статті