ASP.NET Core & EF Core
The middleware pipeline, dependency injection and service lifetimes, model binding, the EF Core change tracker, the N+1 problem, and zero-downtime migrations.
9 questions
JuniorTheoryVery commonWhat is a middleware component in ASP.NET Core, and what does calling next do?
What is a middleware component in ASP.NET Core, and what does calling next do?
A middleware component is a delegate taking the HttpContext and a next delegate. It can inspect the request, call next to pass control to the following component, and then act on the response as the call unwinds. Skipping next short-circuits the pipeline — nothing after it runs.
Common mistakes
- ✗Thinking a component cannot touch the response after it has called
next - ✗Expecting the rest of the pipeline to run even when
nextwas never called - ✗Writing to the response after
nextwhen the headers have already been sent
Follow-up questions
- →Why can you no longer change the headers once
nexthas written the response body? - →How does a terminal component differ from one that calls
next?
JuniorTheoryCommonIn the default host, which value wins for the same key — appsettings.json or an environment variable?
In the default host, which value wins for the same key — appsettings.json or an environment variable?
The environment variable wins. The default host stacks the providers in order — appsettings.json, appsettings.{Environment}.json, user secrets, environment variables, command-line arguments — and the last provider that supplies a key overrides every earlier one.
Common mistakes
- ✗Assuming the JSON file always wins because it is checked into the repository
- ✗Using a colon instead of a double underscore to express nesting in an environment-variable name
- ✗Forgetting that command-line arguments override the environment variables
Follow-up questions
- →How do you express the nested key
ConnectionStrings:Defaultas an environment-variable name? - →Where do user secrets sit in the provider order, and in which environment are they loaded?
JuniorTheoryCommonHow do you register IOrderService in the built-in dependency-injection container so a controller receives it?
How do you register IOrderService in the built-in dependency-injection container so a controller receives it?
Register the mapping on builder.Services — for example AddScoped<IOrderService, OrderService>(). Then declare IOrderService as a constructor parameter: the framework builds the controller through the container and injects the implementation. A type that was never registered throws when it is resolved.
Common mistakes
- ✗Expecting the container to auto-discover an implementation without any registration
- ✗Registering only the concrete class and then asking for the interface
- ✗Reaching for
RequestServicesas a service locator instead of constructor injection
Follow-up questions
- →What happens at resolution time when a required service was never registered?
- →When would you register a factory delegate instead of a type-to-type mapping?
JuniorTheoryCommonWhat does UseExceptionHandler do when a component below it in the pipeline throws?
What does UseExceptionHandler do when a component below it in the pipeline throws?
It wraps everything registered after it in a try/catch. An unhandled exception unwinds back up to it instead of reaching the client, and it re-executes the request on an error path or writes a ProblemDetails body, returning 500. It cannot see what throws above it.
Common mistakes
- ✗Registering it late in the pipeline, so it never sees what throws above it
- ✗Expecting it to resume the failing component rather than unwind the request
- ✗Confusing it with the developer exception page, which is for local diagnostics only
Follow-up questions
- →How does
UseExceptionHandlerdiffer from the developer exception page? - →Where do you register it relative to
UseHttpsRedirectionandUseRouting?
MiddleTheoryCommonWhy does the registration order of the middleware pipeline change the behaviour of a request?
Why does the registration order of the middleware pipeline change the behaviour of a request?
Registration order is execution order — each component wraps everything after it. The exception handler goes first so it catches what happens below; UseRouting precedes UseAuthentication and UseAuthorization, which precede the endpoint, or the endpoint runs with no principal. Short-circuiting early hides the rest.
Common mistakes
- ✗Registering
UseAuthorizationbeforeUseRouting, so endpoint metadata is not known yet - ✗Putting the exception handler after the components whose exceptions it is meant to catch
- ✗Assuming the framework reorders the pipeline into the right order for you
Follow-up questions
- →Why must
UseRoutingrun beforeUseAuthorizationfor the[Authorize]metadata to apply? - →What happens to the response when a component writes to it and then still calls
next?
JuniorTheoryOccasionalWhy does a containerized ASP.NET Core service expose a health-check endpoint?
Why does a containerized ASP.NET Core service expose a health-check endpoint?
The orchestrator cannot see inside the process. MapHealthChecks exposes an endpoint it probes: a failed liveness probe restarts the container, and a failed readiness probe pulls the instance out of the load balancer until its dependencies — database, cache — answer again.
Common mistakes
- ✗Treating liveness and readiness as the same check, so a slow dependency restarts a healthy process
- ✗Probing the database in a liveness check, turning a transient outage into a restart loop
- ✗Assuming the orchestrator infers health from CPU and memory instead of calling an endpoint
Follow-up questions
- →Which dependency belongs in a readiness check but not in a liveness check?
- →How do you keep the health endpoint from being blocked by the authentication middleware?
MiddleTheoryOccasionalWhen does the ASP.NET Core host build the service provider, and which scope resolves a request's services?
When does the ASP.NET Core host build the service provider, and which scope resolves a request's services?
builder.Services collects the descriptors; builder.Build() builds the root IServiceProvider and freezes them, so a later registration throws. Each request gets its own IServiceScope — the endpoint and its dependencies resolve from it, and it is disposed with its scoped IDisposable services once the response completes.
Common mistakes
- ✗Adding registrations to
builder.ServicesafterBuild()and expecting them to apply - ✗Resolving a scoped service straight from the root provider in a background task
- ✗Assuming disposal is the garbage collector's job rather than the scope's
Follow-up questions
- →How does a
BackgroundServicecorrectly resolve a scoped service? - →What does the container do with an
IDisposablesingleton at shutdown?
MiddleTheoryOccasionalHow do Transient, Scoped and Singleton differ, and what is a captive dependency?
How do Transient, Scoped and Singleton differ, and what is a captive dependency?
Transient is a new instance per resolution, Scoped one per request scope, Singleton one per application. A captive dependency is a longer-lived service capturing a shorter-lived one: a Singleton holding a scoped DbContext keeps it alive forever and shares it across concurrent requests and threads. Inject IServiceScopeFactory instead.
Common mistakes
- ✗Injecting a scoped
DbContextinto aSingleton, getting cross-request state and threading bugs - ✗Reading captive dependency as the short-lived service capturing the long-lived one
- ✗Assuming the container re-resolves a captured dependency on every call
Follow-up questions
- →Which startup check catches a captive dependency, and in which environment is it on by default?
- →How does a
BackgroundServiceget aDbContextwithout capturing one?
SeniorTheoryOccasionalWalk a request from the web server Kestrel to the written response — what does each stage do?
Walk a request from the web server Kestrel to the written response — what does each stage do?
Kestrel parses the request into an HttpContext, and the host opens a DI scope for it. The context flows through the middleware pipeline — exception handler, routing (endpoint selection), authentication, authorization — until the endpoint middleware resolves the endpoint's services, binds and validates the model, runs the handler and executes its result. The response then unwinds back out through the pipeline and the scope is disposed.
Common mistakes
- ✗Placing routing after authorization, so the policy has no endpoint metadata to look at
- ✗Thinking result execution happens outside the pipeline, where middleware cannot see the response
- ✗Believing the DI scope spans the whole connection rather than one request
Follow-up questions
- →Where exactly is
HttpContext.Userpopulated, and what has already run by then? - →What can still be changed on the response after its first byte has been flushed?