Skip to main content
Article complete

Get one like this every Tuesday at 7 PM IST.

codewithmukesh
Back to blog
dotnet 28 min read New

30 ASP.NET Core Interview Questions That Actually Get Asked in 2026

30 real ASP.NET Core internals interview questions with great answers, red flags, and follow-ups. Middleware, DI, hosting, configuration - updated for .NET 10.

30 real ASP.NET Core internals interview questions with great answers, red flags, and follow-ups. Middleware, DI, hosting, configuration - updated for .NET 10.

dotnet

interview-questions aspnetcore asp-net-core dotnet-10 csharp interview-prep dotnet-interview aspnet-core-interview middleware dependency-injection di-lifetimes hosting kestrel configuration options-pattern filters model-binding background-service httpcontext request-pipeline

Mukesh Murugan
Mukesh Murugan
Solutions Architect · Microsoft MVP

ASP.NET Core interview questions in 2026 are almost never “what is middleware?” anymore. They’re internals questions: your CORS headers vanish under load, your singleton captures a DbContext and corrupts data across requests, your IOptionsSnapshot won’t inject into a hosted service, your custom middleware throws ObjectDisposedException the moment traffic ramps. Those reveal whether you understand the framework you ship on every day, or just the parts a template scaffolds for you.

I’ve interviewed plenty of .NET developers, and ASP.NET Core internals is where the technical deep-dive round is won or lost. Everyone can add a controller. Far fewer can explain what runs between CreateBuilder and app.Run(), why middleware order isn’t cosmetic, or how the DI container decides what to dispose. This article is 30 ASP.NET Core interview questions in the format that actually gets used: a real scenario, how I’d answer it, the answer that gets you rejected, and the follow-up the interviewer chains next.

This is the internals companion to my .NET Web API interview questions - no REST or status-code trivia here, just the runtime: the pipeline, DI, hosting, configuration, filters, and the request lifecycle. Everything is accurate for ASP.NET Core on .NET 10. Let’s get into it.

What Makes a Good ASP.NET Core Internals Answer?

A strong internals answer names the mechanism, then the consequence. Not “middleware handles requests” but “middleware is a chain of RequestDelegates, each one decides whether to call the next, and that ordering is why auth has to run after routing.” The interviewer is checking whether you’ve debugged the framework, not just used it.

The 30 questions below are grouped into 6 categories that mirror how a technical deep-dive round actually flows. Jump to the one you want to sharpen:

This page is part of my .NET interview prep series. For the broader set, see the .NET interview questions hub, the EF Core interview questions for the data layer, and the .NET Web API interview questions for the REST/API design that sits on top of this runtime.

Practice

Drill ASP.NET Core in the free mock interview

Want to know if these answers actually stick? Take a free, auto-scored mock interview with dedicated ASP.NET Core topics - middleware, DI lifetimes, hosting, and configuration - at Junior, Mid, or Senior level. Instant score, per-topic breakdown, model answer for every question. No signup to start.


Hosting, Startup, and the Host

This is where the interview usually opens - not to trick you, but to see whether you know what your own Program.cs actually does.

Q1. Walk Me Through What Happens Between CreateBuilder and app.Run().

Mid

Program.cs has two distinct phases, and the line builder.Build() is the boundary. Before it, you’re configuring services: WebApplication.CreateBuilder(args) sets up configuration, logging, and the DI container, and you add your registrations to builder.Services. builder.Build() then locks that container and produces the WebApplication. After it, you’re building the request pipeline: each app.UseSomething() appends middleware, and app.Run() starts Kestrel and blocks until shutdown.

The reason this matters: the two phases can’t be interleaved. You can’t register a service after Build(), and you can’t add middleware before it. Knowing the boundary explains half the “why doesn’t this work” bugs juniors hit.

Red flag answer: “It just starts the web server.” - True of the last line only; it misses that the whole DI container and pipeline are assembled first.

Follow-up: “What exactly does CreateBuilder wire up for you by default?”

Q2. Why Does Registering a Service After builder.Build() Throw?

Mid

Because Build() finalizes the service collection into an immutable service provider. Up to that point builder.Services is a mutable IServiceCollection - just a list of descriptors. Build() reads that list, validates it, and produces the root IServiceProvider that will resolve everything for the app’s lifetime. Mutating registrations after the container is built would mean some consumers see a service and others don’t, depending on timing, so the framework forbids it.

The practical takeaway I mention: everything that needs to be injectable has to be registered before Build(). If you find yourself wanting to add a service later, that’s usually a sign you need a factory or an options-configured registration instead.

Red flag answer: “You can add it later with the app’s service provider.” - You can resolve from it, but you can’t register into a built container.

Q3. Kestrel, IIS, and Reverse Proxies - How Is Your App Actually Served?

Mid

Kestrel is the built-in, cross-platform HTTP server that actually runs your ASP.NET Core app - it owns the sockets and turns raw HTTP into an HttpContext. It can run as an edge server facing the internet directly, or behind a reverse proxy like IIS, Nginx, or YARP that handles TLS termination, load balancing, and static content.

The detail interviewers want: when you’re behind a proxy, the proxy talks to Kestrel over localhost, and you need UseForwardedHeaders so the app sees the real client IP and scheme instead of the proxy’s. On Windows/IIS, the ASP.NET Core Module forwards requests into Kestrel - IIS isn’t running your app, it’s fronting Kestrel. Getting this wrong is why “it’s HTTP internally but HTTPS externally” redirect loops happen.

Red flag answer: “IIS runs the app.” - Not since ASP.NET Core; IIS is a reverse proxy in front of Kestrel via the ASP.NET Core Module.

Q4. What Is the Generic Host, and Why Does a Web App Use the Same Host as a Worker Service?

Junior

The Host is the object that owns your app’s lifetime: the DI container, configuration, logging, and the set of IHostedServices that start and stop with the app. WebApplication is built on this same generic host - a web app is just a host that also happens to run Kestrel and a request pipeline. A Worker Service uses the identical host without the web server.

Why it matters: because it’s the same host, everything you know about DI, configuration, and hosted services transfers directly between a web API and a background worker. Your BackgroundService, your options binding, your logging - all host features, not web features.

Red flag answer: “Web apps and worker services are completely different.” - They share the exact same hosting foundation; only the web server and pipeline differ.

Read next

IHostedService vs BackgroundService in .NET 10

The generic host, hosted service lifetimes, startup and shutdown ordering, and the DI-scope trap - the hosting internals this question opens into.


The Middleware Pipeline

This is the single most reliable ASP.NET Core topic in any interview. Expect to explain order, and expect at least one “why is this broken” scenario.

Q5. What Is Middleware, and How Does the Pipeline Execute a Request?

Junior

Middleware is a component that sits in the request pipeline and gets a RequestDelegate pointing at the next component. Each one runs some logic, then decides whether to call next() to pass control down the chain - or to short-circuit and return. On the way back, execution unwinds in reverse, so code after await next() runs as the response bubbles up. That’s the onion model: request goes in through each layer, response comes back out through them in reverse.

app.Use(async (context, next) =>
{
// runs on the way IN
await next(context);
// runs on the way OUT (response is coming back up)
});

Red flag answer: “Middleware handles the request and sends the response.” - It misses that middleware is a chain and that each link controls whether the next one even runs.

Q6. Give Me the Correct Middleware Order, and Explain Why It Matters.

Mid

Order is behavior, not style, because each middleware depends on what ran before it. A typical correct sequence (per the middleware order docs) is: exception handler first (so it wraps everything below), then HSTS and HTTPS redirection, static files, UseRouting, UseCors, UseAuthentication, UseAuthorization, and finally your endpoints.

The reasoning is what they’re grading: authentication has to run before authorization because you can’t decide what a user may do until you know who they are. Routing has to run before auth so the framework knows which endpoint - and which auth requirements - apply. CORS has to sit before auth so preflight OPTIONS requests get their headers before anything rejects them. The classic bug is CORS registered after auth, producing “missing CORS header” errors that look like a framework fault but are pure ordering.

Red flag answer: “Order doesn’t really matter, the framework sorts it out.” - It absolutely matters, and this is the number-one middleware trap.

Read next

ASP.NET Core Middleware in .NET 10

The full request pipeline, the exact order that breaks auth when you get it wrong, custom middleware, short-circuiting, and IMiddleware. The reference behind this whole category.

Q7. What’s the Difference Between Use, Run, and Map?

Mid

Use adds middleware that can call next to continue the pipeline - the normal case. Run adds terminal middleware that never calls next; it ends the pipeline right there. Map branches the pipeline based on the request path, so requests matching a prefix run a separate sub-pipeline.

app.Map("/health", branch =>
branch.Run(async ctx => await ctx.Response.WriteAsync("OK")));

The point to land: Run is where the response is produced and nothing downstream executes. If you accidentally put a Run in the middle of your pipeline, everything after it silently never runs - a subtle way to “lose” middleware.

Red flag answer: “They’re basically the same.” - Then you can’t explain why middleware after a Run never executes.

Q8. How Do You Short-Circuit the Pipeline, and What’s the Bug When You Write to the Response After next()?

Senior

You short-circuit by not calling next() - just write a response (or set a status code) and return. That’s how auth middleware rejects a request with a 401 without running the endpoint.

The bug interviewers probe: once any downstream middleware has started writing the response body, the headers are already sent. If you then try to set a status code or a header in the unwind phase after await next(), you get InvalidOperationException: response has already started. So response-mutating logic (changing the status, adding a header) has to happen before you call next, or you have to buffer the response. This trips people writing “wrap the response” middleware.

app.Use(async (context, next) =>
{
// Safe: set headers BEFORE next writes the body
context.Response.OnStarting(() =>
{
context.Response.Headers["X-Trace-Id"] = trace;
return Task.CompletedTask;
});
await next(context);
// Unsafe here: context.Response.StatusCode = 500; // headers already sent
});

Red flag answer: “I set the status code after await next().” - That throws the moment the endpoint has written anything.

Q9. Where Does Exception-Handling Middleware Go, and What Changed in .NET 10?

Senior

It goes first, or close to it, because middleware only catches exceptions from components registered after it. Put UseExceptionHandler (or your IExceptionHandler pipeline) at the top and it wraps the entire request; put it late and it can’t see failures in earlier middleware.

A currency signal for 2026: in .NET 10 the exception handler middleware no longer writes diagnostic logs for exceptions that an IExceptionHandler handled, and there’s a new ExceptionHandlerOptions.SuppressDiagnosticsCallback to control that per-exception. Before, a handled exception could still spam your logs as if it were unhandled. Mentioning that shows you’re current and that you understand exception handling as a middleware concern, not a try/catch-in-every-controller concern.

app.UseExceptionHandler(new ExceptionHandlerOptions
{
SuppressDiagnosticsCallback = ctx => ctx.Exception is MyKnownException
});

Red flag answer: “I wrap every controller action in try/catch.” - That’s what centralized exception middleware exists to replace.

Read next

Global Exception Handling in ASP.NET Core (.NET 10)

IExceptionHandler, the exception middleware, ProblemDetails responses, and where the handler must sit in the pipeline. Exactly the internals this question tests.

Q10. Your Custom Middleware Needs a Scoped Service. Why Can’t You Inject It in the Constructor?

Senior

Because convention-based middleware is instantiated once, at app startup, and reused for every request - effectively a singleton. If you inject a scoped service (like a DbContext) into its constructor, you capture one instance for the app’s whole lifetime: a captive dependency, with all the thread-safety and stale-state problems that brings.

The fix is to inject scoped services into the InvokeAsync method instead, where the framework resolves them from the current request’s scope on every call:

public class AuditMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context, IAuditService audit) // resolved per request
{
await audit.RecordAsync(context.Request.Path);
await next(context);
}
}

Red flag answer: “I inject the DbContext in the constructor like any other class.” - That’s a captive dependency that shares one context across every request.


Dependency Injection Internals

DI is the framework feature candidates use most and understand least. These questions separate “I call AddScoped” from “I know what the container does with it.”

Q11. Transient vs Scoped vs Singleton - What Does Each Mean for State and Concurrency?

Mid

Transient: a new instance every time it’s resolved - use for lightweight, stateless services. Scoped: one instance per scope, which in a web app means one per HTTP request - use for anything that should be shared within a request but isolated between requests, like a DbContext. Singleton: one instance for the app’s entire lifetime, shared across every request and thread - use for stateless, thread-safe services and caches.

The concurrency angle interviewers want: because a singleton is shared across threads, it must be thread-safe, and because a scoped service is per-request, it must not be captured by a longer-lived one. Lifetime isn’t a style choice, it’s a correctness decision.

Red flag answer: “I just use scoped for everything to be safe.” - Then your stateless helpers allocate per request unnecessarily, and you still hit trouble the moment a singleton needs one.

Read next

Transient vs Scoped vs Singleton in .NET

The three lifetimes, what each means for state and concurrency, and how captive dependencies happen. The foundation for this whole category.

Q12. You Inject a Scoped Service Into a Singleton. What Happens, and How Does the Container Catch It?

Senior

That’s a captive dependency. The singleton is created once and holds whatever it was given forever, so the scoped service it captured never gets replaced - one DbContext, one change tracker, shared across every request. You get thread-safety violations, stale data, and unbounded growth.

How the container catches it: with scope validation on - the default in the Development environment - the DI container validates the dependency graph when the app starts and throws if a singleton depends on a scoped service. In Production that validation is off by default for startup performance, which is exactly why this bug can pass locally and detonate in prod. The fix is to inject IServiceScopeFactory into the singleton and create a scope per unit of work.

Red flag answer: “Nothing, it works fine.” - It works in a demo and corrupts data under concurrency; it’s also caught at startup in Development.

Follow-up: “Why is scope validation off by default in Production?”

Q13. You Register the Same Interface Three Times. What Does Resolving It Give You?

Mid

If you register three implementations of INotifier, resolving INotifier gives you the last one registered - the container uses last-wins for a single resolve. But resolving IEnumerable<INotifier> gives you all three, in registration order. That’s the built-in pattern for “run every handler” designs.

builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
public class Alerts(IEnumerable<INotifier> notifiers) { /* gets both */ }

Red flag answer: “It throws because you registered it twice.” - It doesn’t; multiple registrations are legal and are how IEnumerable<T> injection works.

Read next

Dependency Injection in ASP.NET Core - The Complete .NET 10 Guide

Registration, resolution, IEnumerable injection, factory registrations, and the container internals behind every DI question here.

Q14. What Problem Do Keyed Services Solve?

Mid

Keyed services (from .NET 8 onward) let you register multiple implementations of the same interface under distinct keys and resolve a specific one by key - instead of injecting IEnumerable<T> and filtering, or building a factory by hand.

builder.Services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
builder.Services.AddKeyedScoped<IPaymentGateway, PayPalGateway>("paypal");
public class Checkout([FromKeyedServices("stripe")] IPaymentGateway gateway) { }

The trade-off to mention: keys reintroduce a bit of stringly-typed coupling, so I use them for genuine “pick one of N by name” cases (payment providers, cache backends) and prefer distinct interfaces when the implementations really are different roles.

Red flag answer: “I’d register both and use a big if/switch to pick one.” - That’s the manual wiring keyed services exist to remove.

Q15. Who Disposes the Services the Container Creates, and When?

Senior

The container disposes what it created, tied to the scope that created it. Scoped disposables are disposed when their scope ends - for a web request, at the end of the request. Transient disposables resolved from a scope are also tracked and disposed with that scope, which surprises people: a transient IDisposable isn’t collected immediately, it’s held until the scope closes. Singletons are disposed when the root provider is disposed, at app shutdown.

The catch worth naming: if you construct an instance yourself and register it with AddSingleton(myInstance), the container does not dispose it - you own its lifetime, because you created it, not the container. And a transient disposable resolved from the root provider lives until app shutdown, which is a slow memory leak if you resolve many of them from a singleton.

Red flag answer: “The garbage collector handles disposal.” - The GC frees memory; it does not call Dispose. The container does, and only for what it owns.


Configuration and Options

Configuration questions test whether you’ve deployed across environments or only run on localhost.

Q16. Two Sources Set the Same Config Key. Which Wins, and Why?

Mid

Configuration is layered, and later providers override earlier ones. The default order that CreateBuilder sets up is: appsettings.json, then appsettings.{Environment}.json, then user secrets (in Development), then environment variables, then command-line arguments. So an environment variable beats appsettings.json, and a command-line arg beats everything.

Why it’s built this way: it lets you ship safe defaults in appsettings.json and override per environment without editing files - a container sets ConnectionStrings__Default as an env var and it wins over the checked-in default. Knowing the precedence is how you debug “my setting isn’t taking effect” - something later in the chain is overriding it.

Red flag answer:appsettings.json always wins because it’s the config file.” - Backwards; it’s the base layer that env vars and command line override.

Q17. IOptions vs IOptionsSnapshot vs IOptionsMonitor - When Do You Use Each?

Senior

All three read strongly-typed config bound from IConfiguration (the options pattern), but they differ in lifetime and reload behavior. IOptions<T> is a singleton, computed once, never reloads - fine for config that can’t change at runtime. IOptionsSnapshot<T> is scoped, recomputed once per request, so it picks up config changes between requests - and because it’s scoped, you can’t inject it into a singleton. IOptionsMonitor<T> is a singleton that exposes the current value plus a change callback, so it reloads on the fly and is the one you use inside singletons and background services.

The trap interviewers set: injecting IOptionsSnapshot into a BackgroundService or any singleton fails or captures a stale scope. For long-lived services that need fresh config, it’s IOptionsMonitor.

Red flag answer: “They’re interchangeable, I use whichever autocompletes.” - They have different lifetimes, and mixing them up breaks reload or breaks DI.

Read next

Options Pattern in ASP.NET Core - IOptions vs Snapshot vs Monitor

Binding config to strongly-typed classes, the three interfaces and their lifetimes, validation, and named options. The article behind this exact question.

Q18. How Do You Make a Bad Configuration Fail at Startup Instead of at 3am?

Senior

I validate options and force that validation to run at startup. Binding alone doesn’t validate - a missing connection string just binds to null and blows up on first use, which is exactly the 3am page. Instead I attach data-annotation or delegate validation and call ValidateOnStart() so the app refuses to boot with invalid config:

builder.Services.AddOptions<SmtpOptions>()
.Bind(builder.Configuration.GetSection("Smtp"))
.ValidateDataAnnotations()
.Validate(o => o.Port > 0, "SMTP port must be set")
.ValidateOnStart();

The principle I state: fail fast and fail loud at deploy time, when a human is watching, not at runtime when a customer hits the broken path. This is a cheap way to turn a production incident into a failed deployment.

Red flag answer: “I check the config values inside the service when I use them.” - That defers the failure to runtime instead of catching it at boot.

Q19. How Does the App Pick appsettings.Production.json, and Where Does the Environment Name Come From?

Junior

The environment name comes from the ASPNETCORE_ENVIRONMENT environment variable (or DOTNET_ENVIRONMENT). The host reads it at startup, and the configuration system automatically layers appsettings.{Environment}.json on top of appsettings.json. So setting ASPNETCORE_ENVIRONMENT=Production loads appsettings.Production.json over the base file. If it’s unset, the default is Production.

Where this shows up in code is app.Environment.IsDevelopment() - that’s the same value driving which config file loads and whether the developer exception page turns on. It’s one setting controlling config, error pages, and any of your own environment branches.

Red flag answer: “You change the config file the app reads before you deploy.” - You don’t swap files; you set one environment variable and the framework layers the right file automatically.

Read next

ASP.NET Core Configuration - appsettings, Env Vars & User Secrets

Configuration providers and precedence, environment-based config, user secrets, and how ASPNETCORE_ENVIRONMENT drives it all. The deployment internals this question checks.



Filters and Model Binding

This category tests whether you know the layer above middleware - the part of the pipeline that understands actions, arguments, and results.

Q20. Middleware or a Filter - How Do You Decide?

Mid

Middleware runs on every request and knows nothing about actions, model binding, or ModelState - it sees raw HttpContext. Filters run inside the MVC/endpoint layer, after routing and model binding, so they have action-aware context: the bound arguments, the selected action, the result about to be returned. So the decision is about what context you need.

My rule: cross-cutting concerns that apply to the whole app and don’t need action details - logging, compression, CORS, exception handling - go in middleware. Concerns that need to inspect action arguments or short-circuit a specific endpoint’s result - validation, authorization policies on an action, response shaping - go in filters. Reaching for the wrong layer means either you can’t see the data you need (middleware) or you run logic on non-MVC requests you didn’t mean to (filters can’t help there anyway).

Red flag answer: “They do the same thing, pick either.” - They run at different pipeline stages with different context; that difference is the entire point.

Q21. What’s the Filter Execution Order, and Where Does Short-Circuiting Happen?

Senior

Filters run in a fixed pipeline: authorization filters first, then resource filters, then model binding, then action filters (before and after the action), then exception filters, then result filters (around the result execution). Any filter can short-circuit: an authorization filter that sets a result stops everything below it from running, which is how [Authorize] blocks an action before it executes.

The nuance interviewers like: filters of the same type run outer-to-inner on the way in and inner-to-outer on the way out, and their scope (global, controller, action) plus Order determines the sequence. If two filters fight over the response, order is why one wins.

Red flag answer: “Filters just run before the action.” - Some run before, some after, some around the result, and authorization runs first of all.

Read next

ASP.NET Core Filters in .NET 10 - All 6 Types & Execution Order

Every filter type, the exact execution order, short-circuiting, and how filters differ from middleware. The reference for both filter questions here.

Q22. Action Filters vs Endpoint Filters - What’s the Minimal API Equivalent?

Mid

Action filters belong to the MVC/controller pipeline. Endpoint filters are the Minimal API counterpart - they wrap a route handler’s execution and run after model binding, so they can inspect and modify the bound arguments and the result:

app.MapPost("/orders", (Order o) => Results.Ok(o))
.AddEndpointFilter(async (ctx, next) =>
{
// runs after binding, before the handler
var result = await next(ctx);
return result;
});

They serve the same architectural purpose - post-binding, endpoint-aware cross-cutting logic - but for minimal endpoints instead of controller actions. The distinction interviewers want: endpoint filters still run inside the routing/endpoint layer, so like action filters they see bound arguments that middleware can’t.

Red flag answer: “Minimal APIs don’t support filters.” - They do; endpoint filters are the equivalent, added since .NET 7.

Q23. How Does Model Binding Decide Where to Read a Parameter From?

Mid

Model binding maps incoming request data to your action or handler parameters using a set of binding sources, in a defined precedence. Simple types bind from route values, then the query string. For a complex type the source depends on the controller: a Web API controller marked [ApiController] (and a minimal API) infers the request body as JSON, whereas a plain MVC controller defaults a complex type to form fields - which is exactly why the [ApiController] attribute matters. You can always be explicit with attributes: [FromRoute], [FromQuery], [FromBody], [FromHeader], [FromForm], and [FromServices] to pull a value from DI instead of the request.

The gotcha that separates a real answer: only one parameter can bind from the body per request, because the body is a forward-only stream that’s read once. Try to [FromBody] two parameters and the second gets nothing. In minimal APIs the same rules apply, inferred from the parameter types unless you annotate them.

Red flag answer: “It just matches parameter names to the JSON.” - That ignores route/query/header sources and the single-body-parameter rule.

Read next

Minimal APIs in ASP.NET Core .NET 10 - Routing, Binding & Validation

Endpoint routing, the binding sources and their precedence, inferred binding, and the single-body-parameter rule. Directly relevant to model binding questions.

Q24. In .NET 10, How Do You Validate Input in a Minimal API Without FluentValidation?

Mid

.NET 10 added built-in validation for Minimal APIs. You call AddValidation() to register the validation services, and the framework wires an endpoint filter that validates parameters using System.ComponentModel.DataAnnotations attributes - and crucially those attributes now work on record types, so your request DTOs stay concise:

builder.Services.AddValidation();
app.MapPost("/products",
(Product p) => Results.Ok(p)); // validated automatically
public record Product([Required] string Name, [Range(1, 1000)] int Quantity);

On failure it returns a 400 with problem details, and you can customize the response through IProblemDetailsService. The trade-off I mention: data annotations cover the common cases, but for cross-field rules or complex conditional logic I still reach for FluentValidation. Knowing the built-in path exists now is the 2026 currency signal - before .NET 10, minimal APIs had no built-in validation at all.

Red flag answer: “Minimal APIs can’t validate, you have to do it manually.” - Correct before .NET 10, wrong now; AddValidation() is built in.

Read next

FluentValidation in ASP.NET Core .NET 10 - Beyond Data Annotations

When the built-in data-annotation validation isn't enough - cross-field rules, conditional validation, and clean separation. The complement to this question.


Request Lifecycle and Background Work

This is the senior end of the round. These questions are about the runtime model - threads, context, lifetime - where copy-paste knowledge falls apart.

Q25. What Is HttpContext, What’s Its Lifetime, and Why Is Capturing It a Bug?

Senior

HttpContext is the per-request object holding everything about the current request and response - headers, the user, the request-scoped service provider, the body streams. Its lifetime is exactly one request: it’s created when Kestrel accepts the request and disposed when the response completes. It is not thread-safe and it’s not valid after the request ends.

That’s why capturing it is a bug. If you stash HttpContext (or its User, or Request) in a field, a static, or a closure that runs later - a background continuation, a fire-and-forget task - you’re touching a disposed object, which throws ObjectDisposedException or reads garbage. If you need request data after the request, you copy the values out while the request is alive and pass those, never the context itself.

Red flag answer: “I keep a reference to HttpContext so I can use it in the background job.” - That reference is invalid the moment the request completes.

Read next

ASP.NET Core Middleware in .NET 10

HttpContext, the request pipeline, and how the per-request context flows through middleware - the request-lifecycle internals behind this question.

Q26. Within One Request, Does Everything Run on One Thread? What Happens Across an await?

Senior

No. A request is not pinned to one thread. When your code hits an await on real I/O, the thread is released back to the thread pool while the I/O completes, and the continuation after the await may resume on a different thread. That thread-hopping is exactly why async scales - a thread isn’t sitting idle waiting on the database.

The detail that signals depth: ASP.NET Core has no SynchronizationContext, unlike old ASP.NET. So there’s no “UI thread” to marshal back to, ConfigureAwait(false) isn’t needed for correctness in app code, and any code that assumes thread affinity within a request - using [ThreadStatic], or a lock that expects the same thread - is broken. Request-scoped state lives in DI or HttpContext.Items, not on the thread.

Red flag answer: “One request runs on one thread start to finish.” - Wrong for any async code; the continuation can resume on another thread.

Q27. You Need the Current User Inside a Singleton Service. How, and What’s the Catch?

Senior

You inject IHttpContextAccessor and read HttpContextAccessor.HttpContext when you need it. It works from a singleton because it doesn’t capture the context - it resolves the current request’s context on each access, backed by an AsyncLocal that flows with the async call chain.

The catches worth naming: it returns null when there’s no active request (a background service, app startup), so you guard for that. It carries a small performance cost from the AsyncLocal lookup, so I don’t sprinkle it everywhere. And it must be registered with AddHttpContextAccessor(). The cleaner pattern where possible is to pass the user info in as a parameter from the request-scoped layer, rather than reaching into ambient context from deep in a singleton.

Red flag answer: “I inject HttpContext directly into the singleton.” - You can’t; HttpContext isn’t registered and would be a captive dependency anyway. IHttpContextAccessor is the indirection that makes it safe.

Q28. An Unauthenticated API Call Returns 401 in .NET 10 Where It Used to Redirect to Login. What Changed?

Senior

This is a real .NET 10 behavior change in cookie authentication. Historically, an unauthenticated request triggered a 302 redirect to the login page even for API endpoints, which is wrong for an API - a JSON client wants a 401, not an HTML login page. In .NET 10, requests to known API endpoints now return 401/403 instead of redirecting.

How the framework decides: a new IApiEndpointMetadata marker identifies API endpoints, and it’s applied automatically to [ApiController] actions, minimal APIs that read or write JSON, endpoints using TypedResults, and SignalR. So the framework can tell “this is an API call” from “this is a browser page” and respond appropriately. If you need the old redirect behavior you override OnRedirectToLogin. Knowing this is a strong currency signal and shows you understand auth as pipeline behavior, not just [Authorize].

Red flag answer: “The 401 must be a bug in my auth setup.” - It’s an intentional .NET 10 change; API endpoints stop getting login redirects.

Read next

JWT Authentication in ASP.NET Core - A Complete .NET 10 Guide

How the authentication middleware establishes identity, challenge vs forbid, and how the scheme decides 401 vs redirect. The auth internals behind this question.

Q29. Your BackgroundService Needs a DbContext. Why Is Injecting It a Trap, and How Does Shutdown Work?

Senior

A BackgroundService is registered as a singleton - it lives for the app’s lifetime. A DbContext is scoped. Injecting the context into the service constructor is a captive dependency: one context captured forever, its change tracker growing without bound, shared across every loop iteration. The correct pattern is to inject IServiceScopeFactory and create a fresh scope per unit of work:

public class Worker(IServiceScopeFactory scopeFactory) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// fresh, short-lived context per iteration
await Task.Delay(1000, stoppingToken);
}
}
}

On shutdown, the host signals the stoppingToken, and your loop should observe it and exit cleanly within the shutdown timeout - that’s graceful shutdown. Ignoring the token means in-flight work gets killed when the timeout expires.

Red flag answer: “I inject the DbContext straight into the background service.” - Captive dependency; it corrupts state and never releases the context.

Read next

IHostedService vs BackgroundService in .NET 10: Which to Use

Hosted service lifetimes, the scope-factory pattern for scoped dependencies, startup and shutdown ordering, and graceful shutdown. The exact internals this question tests.

Q30. Walk Me Through the Full Journey of a Request, From the Socket to Your Endpoint.

Senior

This is the synthesis question, and a clean walkthrough proves you understand the whole runtime:

  1. Kestrel accepts the TCP connection and parses the raw HTTP into an HttpContext, allocating a request-scoped IServiceProvider.
  2. The request enters the middleware pipeline in registration order - exception handling, HTTPS, static files, and so on.
  3. UseRouting matches the request to an endpoint; UseAuthentication and UseAuthorization establish and check identity for that endpoint.
  4. At the endpoint, model binding reads the request into your parameters, and the filter/endpoint-filter pipeline runs around the handler.
  5. Your handler executes, resolving scoped services from the request’s provider.
  6. The result is written to the response, execution unwinds back up the middleware in reverse, and finally the request scope is disposed - releasing scoped services and the DbContext.

The value is showing the layers in order and where DI scope, routing, and the pipeline each fit. If you can narrate this, every earlier question has a home in the story.

Red flag answer: “The request comes in and my controller runs.” - It skips Kestrel, the pipeline, routing, binding, and scope disposal - the entire framework.


5 ASP.NET Core Interview Mistakes That Get Developers Rejected

After interviewing plenty of .NET developers, these are the patterns that sink an otherwise decent candidate on the internals round:

  1. Treating middleware order as cosmetic. If you can’t explain why auth runs after routing and CORS runs before auth, the interviewer assumes you’ve never debugged a pipeline.
  2. Not understanding DI lifetimes. “I use scoped everywhere” or “singleton so it’s reused” without knowing captive dependencies signals you’ve never traced a concurrency bug to its source.
  3. Thinking a request is one thread. Assuming thread affinity - [ThreadStatic], expecting HttpContext in a background task - reveals you don’t understand how async actually executes.
  4. Being two versions behind. Mentioning Startup.cs with Configure/ConfigureServices, IHostBuilder ceremony, or “minimal APIs can’t validate” tells the interviewer you stopped learning before .NET 8.
  5. Reciting definitions with no mechanism. “Middleware processes requests” is a C answer. “Middleware is a chain of RequestDelegates where each one decides whether to call the next” is an A answer.

Key Takeaways

  • The pipeline is the core concept. Middleware order, short-circuiting, and the onion model explain most ASP.NET Core behavior - master it first.
  • DI lifetimes are correctness, not style. Captive dependencies, scope validation, and disposal ownership are the questions that separate mid from senior.
  • Configuration and options test deployment experience. Provider precedence, IOptionsSnapshot vs IOptionsMonitor, and ValidateOnStart come from shipping across environments.
  • Know the .NET 10 changes. Built-in minimal API validation, the cookie-auth 401-vs-redirect change, and the exception-handler diagnostics change are current signals interviewers listen for.
  • Every strong answer names the mechanism. State what the framework does under the hood, then the consequence, then your default.
Free resource Companion download

.NET Interview Questions

300+ real .NET interview questions with answers, red flags, and follow-ups - C#, EF Core, ASP.NET Core, system design

What are the most common ASP.NET Core interview questions in 2026?

The most common ASP.NET Core interview questions in 2026 are internals and scenario based: the correct middleware order and why it matters, the difference between transient, scoped, and singleton lifetimes, what a captive dependency is, how the request pipeline executes with the onion model, the difference between IOptions, IOptionsSnapshot, and IOptionsMonitor, and how HttpContext and its lifetime work. Interviewers ask these as debugging scenarios rather than definitions to see whether you have actually built and troubleshooted ASP.NET Core applications.

What is the correct middleware order in ASP.NET Core?

A typical correct middleware order is exception handling first, then HSTS and HTTPS redirection, static files, UseRouting, UseCors, UseAuthentication, UseAuthorization, and finally the endpoints. Order matters because each middleware depends on what ran before it. Authentication must run before authorization because you cannot decide what a user may do until you know who they are, routing must run before authentication so the framework knows which endpoint applies, and CORS must run before authentication so preflight requests get their headers.

What is the difference between transient, scoped, and singleton in ASP.NET Core?

Transient services are created new every time they are resolved and suit lightweight stateless services. Scoped services are created once per scope, which in a web app means once per HTTP request, and suit request-shared state like a DbContext. Singleton services are created once for the app lifetime and are shared across every request and thread, so they must be thread safe. Injecting a scoped service into a singleton creates a captive dependency, which shares one instance forever and causes concurrency and stale-state bugs.

What is a captive dependency in ASP.NET Core?

A captive dependency happens when a longer-lived service captures a shorter-lived one, most commonly a singleton that depends on a scoped service. The singleton is created once and holds the scoped instance forever, so a per-request service like a DbContext ends up shared across every request, causing thread-safety violations, stale data, and unbounded change-tracker growth. The DI container detects this at startup when scope validation is on, which is the default in Development. The fix is to inject IServiceScopeFactory and create a scope per unit of work.

How does the ASP.NET Core middleware pipeline work?

The middleware pipeline is a chain of components, each receiving a RequestDelegate that points to the next component. Each middleware runs logic, then decides whether to call next to continue the chain or to short-circuit and return. On the way back, execution unwinds in reverse order, so code after await next runs as the response bubbles up. This is called the onion model. Middleware is added in registration order, and terminal middleware added with Run never calls next and ends the pipeline.

What is the difference between IOptions, IOptionsSnapshot, and IOptionsMonitor?

IOptions is a singleton computed once that never reloads, suitable for config that cannot change at runtime. IOptionsSnapshot is scoped and recomputed once per request, so it picks up config changes between requests, but because it is scoped it cannot be injected into a singleton. IOptionsMonitor is a singleton that exposes the current value plus a change callback, so it reloads on the fly and is the correct choice inside singletons and background services. A common mistake is injecting IOptionsSnapshot into a BackgroundService, which fails because of the lifetime mismatch.

What changed in ASP.NET Core validation and authentication in .NET 10?

In .NET 10, ASP.NET Core added built-in validation for Minimal APIs. Calling AddValidation registers validation services and an endpoint filter that validates parameters using DataAnnotations attributes, which now work on record types, returning a 400 with problem details on failure. Authentication also changed: unauthenticated requests to known API endpoints now return 401 or 403 instead of redirecting to a login page, decided by a new IApiEndpointMetadata marker applied to ApiController actions, JSON minimal APIs, TypedResults endpoints, and SignalR.

What is HttpContext and how long does it live in ASP.NET Core?

HttpContext is the per-request object that holds everything about the current request and response, including headers, the authenticated user, the request-scoped service provider, and the request and response streams. It lives for exactly one request, created when the request is accepted and disposed when the response completes. It is not thread safe and is invalid after the request ends, so capturing it in a field, static, or background continuation causes ObjectDisposedException. To use request data later, copy the values out while the request is alive rather than storing the context.


If a question here exposed a gap, the FREE .NET Web API Zero to Hero course teaches ASP.NET Core in order - hosting, the middleware pipeline, dependency injection, configuration, filters, and the request lifecycle - with runnable code and no paywall. It’s the fastest way to turn a shaky internals answer into a confident one.

This is one spoke of my broader interview prep series. Start at the .NET interview questions hub for the cross-topic greatest hits, go deeper on the data layer with the EF Core interview questions, and read the .NET Web API interview questions for the REST and API-design layer that sits on top of this runtime.

If this helped, bookmark it for your next interview, or share it with someone prepping for theirs.

Happy Coding :)

View all articles

What's your take?

Push back, share a war story, or ask the obvious question someone else is wondering. I read every comment.

View on GitHub

Weekly .NET tips · free

Newsletter

stay ahead in .NET

One email every Tuesday at 7 PM IST. One topic, deep. The week's articles. No filler.

Tutorials Architecture DevOps AI
Join 9,735 developers · Delivered every Tuesday
Privacy notice 30s read

Cookies, but only the useful ones.

I use cookies to understand which articles get read and which CTAs actually work. No third-party advertising trackers, ever. Read the privacy policy →