Top 25 pytań rekrutacyjnych ASP.NET Core: Middleware, DI i Minimal APIs
25 zaawansowanych pytań rekrutacyjnych ASP.NET Core z odpowiedziami: middleware pipeline, dependency injection, Minimal APIs i wzorce architektoniczne.

Rozmowy kwalifikacyjne dla programistów ASP.NET Core w 2026 roku wymagają znacznie więcej niż tylko znajomości składni języka C#. Rekruterzy poszukują kandydatów, którzy rozumieją architektury aplikacji webowych od podstaw — od tego, jak żądania HTTP przechodzą przez potok middleware, przez mechanizmy wstrzykiwania zależności, aż po nowoczesne podejście do budowania API z wykorzystaniem Minimal APIs. W tym artykule przedstawiamy 25 pytań rekrutacyjnych, które regularnie pojawiają się podczas rozmów kwalifikacyjnych na stanowiska związane z ASP.NET Core, wraz z praktycznymi przykładami kodu i szczegółowymi odpowiedziami.
Podczas rozmowy kwalifikacyjnej na stanowisko ASP.NET Core developer, rekruterzy nie sprawdzają jedynie teoretycznej wiedzy. Szukają dowodów, że kandydat rozumie konsekwencje decyzji architektonicznych — dlaczego kolejność middleware ma znaczenie, jak uniknąć memory leaków w dependency injection, i kiedy Minimal APIs są lepszym wyborem niż tradycyjne kontrolery. Pytania o middleware, DI i Minimal APIs są często punktem wyjścia do głębszej dyskusji o rzeczywistych problemach produkcyjnych.
ASP.NET Core Middleware Pipeline — pytania rekrutacyjne
1. Wyjaśnij, czym jest middleware w ASP.NET Core i dlaczego kolejność rejestracji ma znaczenie
Middleware to komponenty programistyczne, które tworzą potok przetwarzania żądań HTTP w ASP.NET Core. Każdy middleware może przetwarzać żądanie przed przekazaniem go dalej oraz modyfikować odpowiedź w drodze powrotnej. Kolejność rejestracji jest krytyczna, ponieważ określa kolejność wykonywania.
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();Jeśli umieścimy UseAuthorization przed UseAuthentication, autoryzacja będzie sprawdzana dla niezidentyfikowanego użytkownika, co zawsze zakończy się odmową dostępu. Podobnie, UseExceptionHandler musi być pierwszym middleware, aby przechwycić wyjątki ze wszystkich kolejnych komponentów.
2. Jak napisać własny middleware i jakie są dwa sposoby jego rejestracji?
Własny middleware implementuje metodę InvokeAsync lub Invoke, która przyjmuje HttpContext i wywołuje delegat _next do kontynuacji potoku.
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>();Dwa sposoby rejestracji:
- Przez klasę:
app.UseMiddleware<RequestTimingMiddleware>()— zalecane dla middleware z zależnościami DI - Inline z delegatem:
app.Use(async (context, next) => { /* logic */ })— szybkie prototypowanie lub prosta logika
3. Jaka jest różnica między app.Use() a app.Run()?
app.Use() rejestruje middleware, który może wywołać następny komponent w potoku przez wywołanie next(). app.Run() rejestruje terminal middleware — końcowy komponent, który nigdy nie wywołuje kolejnych middleware i zawsze kończy przetwarzanie żądania.
Praktyczna konsekwencja: Jeśli użyjemy app.Run() wcześnie w potoku (np. przed UseRouting), wszystkie kolejne middleware będą zignorowane. To częsta przyczyna błędów podczas debugowania.
4. Co to jest short-circuiting middleware i kiedy jest stosowane?
Short-circuiting middleware to taki, który nie wywołuje next(), przerywając tym samym dalsze przetwarzanie potoku. Przykłady:
- UseStaticFiles — jeśli znajdzie plik statyczny (CSS, JS, obraz), zwraca go bezpośrednio i przerywa potok, nie docierając do routingu czy kontrolerów
- Własne middleware do rate limiting — jeśli limit jest przekroczony, zwraca
429 Too Many Requestsi nie wywołuje kolejnych komponentów
Short-circuiting zwiększa wydajność, ponieważ unika niepotrzebnego przetwarzania dla zasobów, które można obsłużyć wcześnie.
5. Jak obsłużyć wyjątki globalnie w ASP.NET Core?
ASP.NET Core oferuje dwa główne mechanizmy:
UseExceptionHandler: Przechwytuje wyjątki i przekierowuje do określonej ścieżki obsługi błędów. Zalecane dla aplikacji MVC/Razor Pages.
Developer Exception Page: W środowisku deweloperskim wyświetla szczegółowe informacje o wyjątku. Dla API często lepszym rozwiązaniem jest własny middleware do obsługi wyjątków, który zwraca odpowiedzi JSON z odpowiednimi kodami statusu HTTP zamiast przekierowywać na stronę HTML.
6. Czy można modyfikować odpowiedź po wywołaniu next() w middleware?
Tak, ale z ważnym zastrzeżeniem: można modyfikować nagłówki i treść odpowiedzi przed rozpoczęciem wysyłania do klienta. Gdy bufor odpowiedzi zacznie być przesyłany (streaming), modyfikacja nagłówków jest niemożliwa.
Praktyczne zastosowanie to middleware do kompresji odpowiedzi lub dodawania niestandardowych nagłówków bezpieczeństwa. Aby bezpiecznie modyfikować treść odpowiedzi, należy podmienić Response.Body na własny MemoryStream, przetworzyć całą odpowiedź, a następnie zapisać zmodyfikowaną wersję do oryginalnego strumienia.
Dependency Injection — pytania rekrutacyjne
7. Wyjaśnij różnicę między Singleton, Scoped i Transient w ASP.NET Core DI
Lifecycle określa, jak długo żyje instancja serwisu i kiedy jest tworzona:
Singleton: Jedna instancja dla całego czasu życia aplikacji. Tworzona przy pierwszym żądaniu i współdzielona przez wszystkie żądania i wątki. Używana dla bezstanowych serwisów lub cache'u w pamięci.
Scoped: Jedna instancja na jedno żądanie HTTP. Nowa instancja dla każdego żądania, ale współdzielona przez wszystkie komponenty w ramach tego samego żądania. Standardowy wybór dla repozytoriów i serwisów biznesowych.
Transient: Nowa instancja przy każdym żądaniu serwisu z DI. Używana dla lekkich, bezstanowych serwisów. Może prowadzić do problemów z wydajnością, jeśli serwis jest kosztowny w inicjalizacji.
8. Co to jest captive dependency i jak go uniknąć?
Captive dependency występuje, gdy serwis o dłuższym czasie życia (Singleton) przechowuje referencję do serwisu o krótszym czasie życia (Scoped lub Transient). To powoduje, że krótkotrwały serwis faktycznie żyje tak długo jak Singleton, co prowadzi do memory leaków i nieprawidłowego stanu.
Jeśli ICacheService (Singleton) ma w konstruktorze IUserRepository (Scoped), to repository będzie żyło przez cały czas życia aplikacji, utrzymując otwarte połączenie do bazy danych i potencjalnie zwracając nieaktualne dane. ASP.NET Core wykryje to w trybie Development, jeśli włączysz ValidateScopes.
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
});Rozwiązanie: Wstrzyknij IServiceProvider do Singletona i rozwiązuj Scoped serwisy przez utworzenie scope w metodach, które ich potrzebują.
9. Jak działa Keyed Services w .NET 8+ i kiedy jest to przydatne?
Keyed Services pozwala rejestrować wiele implementacji tego samego interfejsu i rozwiązywać je po kluczu (string lub enum). To eliminuje potrzebę tworzenia osobnych interfejsów dla każdej implementacji.
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
}
}Przydatne w scenariuszach takich jak:
- Wiele dostawców powiadomień (email, SMS, push)
- Różne strategie cache'owania (memory, Redis, distributed)
- Multi-tenancy — osobne implementacje per tenant
10. Jak zarejestrować serwis tylko wtedy, gdy nie jest jeszcze zarejestrowany?
Użyj metod TryAdd*. W przeciwieństwie do AddScoped, które zawsze dodaje rejestrację (nawet jeśli taka już istnieje), TryAddScoped dodaje serwis tylko jeśli interfejs nie jest jeszcze zarejestrowany. To przydatne w bibliotekach i pakietach NuGet, które chcą zapewnić domyślną implementację, ale pozwolić aplikacji ją nadpisać.
11. Co to jest Open Generic Registration i kiedy jest używana?
Open Generic Registration pozwala rejestrować generyczne typy bez określania konkretnego typu parametru. ASP.NET Core automatycznie rozwiąże właściwą wersję generyczną w runtime. To eliminuje potrzebę ręcznej rejestracji IRepository<Product>, IRepository<User> itd. Często stosowane w implementacjach Repository Pattern lub Generic Unit of Work.
12. Jak implementować Decorator Pattern z ASP.NET Core DI?
Decorator Pattern pozwala "owijać" serwis dodatkową funkcjonalnością bez modyfikowania oryginalnej implementacji. Typowe przypadki użycia to logging, caching czy retry logic.
// 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);
});CachedProductService implementuje IProductService, przyjmuje w konstruktorze oryginalny ProductService i dodaje logikę cache'owania przed delegowaniem wywołań do wewnętrznej implementacji. To pozwala łatwo włączać/wyłączać cache bez zmiany kodu biznesowego.
Gotowy na rozmowy o .NET?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Minimal APIs — pytania rekrutacyjne
13. Czym różnią się Minimal APIs od tradycyjnych kontrolerów MVC?
Minimal APIs to lżejsze podejście do budowania HTTP API wprowadzone w .NET 6. Główne różnice:
Minimal APIs:
- Endpoints definiowane jako funkcje w
Program.cslub osobnych klasach statycznych - Brak klas kontrolerów, atrybutów routing, dziedziczenia po
ControllerBase - Mniejszy overhead, szybsze cold start
- Idealne dla mikrousług i prostych API
Kontrolery MVC:
- Organizacja w klasach z metodami akcji
- Bogate wsparcie dla filtrów, model binding, walidacji przez atrybuty
- Bardziej ustrukturyzowane dla złożonych API z wieloma akcjami
Wybór zależy od złożoności projektu. Dla prostych CRUD API, Minimal APIs są często wystarczające i bardziej czytelne.
14. Jak organizować Minimal API endpoints w dużych projektach?
Zamiast umieszczać wszystkie endpointy w Program.cs, stosujemy pattern z statycznymi klasami i metodami rozszerzającymi:
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();To organizuje endpointy według domeny biznesowej (Products, Orders, Users), utrzymując Program.cs czysty i czytelny.
15. Jak działa automatyczne wiązanie parametrów w Minimal APIs?
ASP.NET Core automatycznie wiąże parametry metod endpoint na podstawie ich źródła:
- Route parameters:
int idw/{id:int}jest automatycznie wiązany - Query string:
string? searchwiąże się z?search=term - Body: Złożone typy (klasy, rekordy) są deserializowane z JSON body
- Services: Parametry typu zarejestrowanego w DI są rozwiązywane automatycznie
- Specjalne typy:
HttpContext,HttpRequest,HttpResponse,CancellationToken
Można wymusić źródło przez atrybuty: [FromRoute], [FromQuery], [FromBody], [FromServices], [FromHeader].
16. Co to jest Route Group i jakie daje korzyści?
Route Groups (MapGroup) pozwalają grupować endpointy z wspólnym prefiksem i wspólną konfiguracją (autoryzacja, tagi OpenAPI, rate limiting).
Korzyści:
- DRY: Nie powtarzasz
/api/adminw każdym endpoint - Centralna konfiguracja: Autoryzacja, rate limiting raz dla całej grupy
- Lepsze OpenAPI docs: Automatyczne grupowanie w Swagger UI
17. Jak zaimplementować walidację w Minimal APIs?
Minimal APIs nie mają wbudowanej walidacji jak kontrolery (Data Annotations), ale można użyć Endpoint Filters z FluentValidation lub ręczną walidacją. Alternatywnie, można stworzyć globalny Endpoint Filter, który automatycznie waliduje wszystkie requesty mające validator w DI.
18. Jaka jest różnica między Results a TypedResults?
Results: Zwraca IResult — niegeneryczny interfejs. Przykład: Results.Ok(data).
TypedResults: Zwraca generyczne typy jak Ok<T>, NotFound<T> — umożliwia pełne type safety i lepsze wsparcie dla OpenAPI document generation. Przykład: TypedResults.Ok(product).
TypedResults jest zalecany w nowych projektach .NET 7+ dla lepszej integracji z OpenAPI i bezpieczeństwa typów w czasie kompilacji.
Zaawansowane pytania rekrutacyjne
19. Jak testować middleware w izolacji?
Testowanie middleware wymaga:
- Fake RequestDelegate: Symuluje następny komponent w potoku
- DefaultHttpContext: Dostarcza mockowy kontekst HTTP
- Fake Logger: Przechwytuje logi do asercji
[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);
}Dzięki temu można testować logikę middleware bez uruchamiania całego potoku HTTP czy serwera testowego.
20. Jak działa Options Pattern i dlaczego jest zalecany dla konfiguracji?
Options Pattern używa klas POCO do reprezentowania ustawień konfiguracyjnych i wiązania ich z appsettings.json. Zalety to silne typowanie, walidacja, łatwe testowanie i automatyczne przeładowanie po zmianie konfiguracji.
21. Co to jest IHttpClientFactory i dlaczego nie powinno się tworzyć HttpClient bezpośrednio?
Bezpośrednie tworzenie new HttpClient() prowadzi do wyczerpania portów sieciowych (socket exhaustion) i problemów z DNS refresh. IHttpClientFactory rozwiązuje te problemy przez pooling connection, automatyczne odświeżanie DNS co 2 minuty, named/typed clients i integrację z Polly dla retry policies.
22. Jak implementować Background Services w ASP.NET Core?
Background Services dziedziczą po BackgroundService i wykonują długotrwałe zadania w tle (przetwarzanie kolejek, scheduled jobs). Background Service jest Singleton, więc Scoped serwisy muszą być rozwiązywane przez CreateScope().
23. Wyjaśnij różnicę między sync I/O a async I/O w kontekście ASP.NET Core
Sync I/O: Wątek jest blokowany podczas operacji I/O. W ASP.NET Core thread pool jest ograniczony, więc zablokowane wątki mogą doprowadzić do thread starvation.
Async I/O: Wątek jest zwracany do thread pool podczas oczekiwania na I/O. To pozwala obsługiwać setki równoczesnych żądań z małą liczbą wątków.
Praktyczna konsekwencja: Nigdy nie używaj .Result ani .Wait() w ASP.NET Core — zawsze await.
24. Co to jest Output Caching w .NET 8+ i czym różni się od Response Caching?
Response Caching: Cache po stronie klienta kontrolowany przez nagłówki HTTP.
Output Caching: Cache po stronie serwera. Całkowicie pomija pipeline wykonania dla cachowanych żądań — endpoint nawet nie jest wywoływany. Znacznie szybsze niż Response Caching. Wspiera tag-based invalidation.
25. Jak działa Rate Limiting w .NET 7+ i jakie są dostępne algorytmy?
Rate Limiting ogranicza liczbę żądań od klienta w określonym czasie. ASP.NET Core 7+ oferuje wbudowane algorytmy: Fixed Window, Sliding Window, Token Bucket i Concurrency Limiter. Każdy z nich ma inne zastosowania i kompromisy między prostotą a precyzją kontroli.
Teoretyczna wiedza to fundament, ale rekruterzy oczekują praktycznych dowodów. Najlepsza strategia przygotowania to budowanie projektów, które demonstrują zrozumienie pipeline middleware, DI i nowoczesnych wzorców API. Zamiast zapamiętywać odpowiedzi, implementuj przykłady z tego artykułu we własnym projekcie — to pozwoli odpowiadać z przekonaniem i omawiać rzeczywiste decyzje architektoniczne podczas rozmowy.
Podsumowanie — kluczowe punkty
- Middleware Pipeline: Zrozumienie kolejności wykonywania, short-circuiting, globalnej obsługi błędów i implementacji własnych komponentów to podstawa każdej aplikacji ASP.NET Core
- Dependency Injection: Znajomość lifecycle (Singleton, Scoped, Transient), captive dependencies, Keyed Services i zaawansowanych wzorców jak Decorator Pattern to wyznacznik doświadczenia produkcyjnego
- Minimal APIs: Nowoczesne podejście do budowania HTTP API wymaga znajomości organizacji endpointów, Route Groups, automatycznego wiązania parametrów i różnic względem tradycyjnych kontrolerów
- Zaawansowane tematy: Rate Limiting, Output Caching, Background Services, async I/O i Options Pattern to elementy, które odróżniają seniorów od juniorów podczas technicznych dyskusji
Kluczem do sukcesu na rozmowie kwalifikacyjnej nie jest zapamiętywanie składni, ale rozumienie dlaczego dane podejście jest stosowane i jakie problemy rozwiązuje. Rekruterzy poszukują kandydatów, którzy potrafią uzasadnić swoje decyzje architektoniczne i przewidzieć konsekwencje różnych wyborów technologicznych.
Platforma SharpSkill oferuje interaktywne testy techniczne, flashcardy i symulator rozmów kwalifikacyjnych dla programistów ASP.NET Core. Praktyczne scenariusze oparte na rzeczywistych pytaniach rekrutacyjnych pozwalają przygotować się do rozmów w kontrolowanych warunkach i zidentyfikować luki w wiedzy przed rzeczywistym interview. Sprawdź testy z ASP.NET Core i przećwicz odpowiedzi na wszystkie 25 pytań z tego artykułu w interaktywnej formie.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Pytania rekrutacyjne C# i .NET: Kompletny przewodnik 2026
17 najczesciej zadawanych pytan rekrutacyjnych z C# i .NET. LINQ, async/await, Dependency Injection, Entity Framework Core i ASP.NET Core ze szczegolowymi odpowiedziami.

.NET MAUI w 2026: Kompletny Przewodnik po Programowaniu Cross-Platform i Pytania Rekrutacyjne
Poznaj .NET MAUI 10 w 2026 roku - od konfiguracji projektu, przez architekturę handlerów, MVVM z CommunityToolkit, HybridWebView, po migrację z Xamarin i kluczowe pytania rekrutacyjne.

.NET 9 Blazor: Pełnostackowy rozwój aplikacji z Blazor United w 2026
.NET 9 Blazor United łączy statyczny SSR, Server i WebAssembly w jednym frameworku full-stack. Praktyczny poradnik obejmujący tryby renderowania, streaming rendering, wstrzykiwanie zależności i wzorce produkcyjne.