ASP.NET Core
ASP.NET Core is simpler than its attribute surface suggests. The Kestrel web server parses the socket into an HttpContext, the host opens a DI scope for the request, and from there the context is simply threaded through a chain of delegates — the middleware pipeline. Every component sees the request on the way down, calls next, and sees the response on the way back up. The endpoint (a controller or a Minimal API) is the last link of that chain, not a separate thing standing beside it.
Every trap in this topic grows out of that construction, and they are all about order and about lifetime. Middleware registration order is semantic: UseExceptionHandler catches only what is registered below it, UseAuthentication must precede UseAuthorization, and terminal middleware never calls next — so nothing after it runs. A service's lifetime decides how long the references it captures live: a Singleton that takes a Scoped DbContext in its constructor keeps it for the lifetime of the process — that is a captive dependency, and in production it means cross-request state corruption and a DbContext used concurrently by parallel requests. Configuration is a stack of providers where the last one to supply a key wins — which is why an environment variable beats appsettings.json, not the other way round. The layer-by-layer breakdown is below.
Topic map
- The middleware pipeline — a chain of delegates, the two phases around
next, short-circuiting, and why order is semantic. - Dependency injection —
IServiceCollectionfreezes atBuild(), constructor injection, the request scope and its disposal. - Service lifetimes —
Transient/Scoped/Singleton, and the captive dependency as the headline bug of the topic. - Configuration precedence — the provider stack, last-one-wins, environment variables versus
appsettings.json, secrets. - Exception handling —
UseExceptionHandlernear the top of the pipeline,ProblemDetails,IExceptionHandler. - Health checks — liveness versus readiness, and why an orchestrator must never confuse them.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
Registering UseExceptionHandler late in the pipeline | It only wraps what sits below it — exceptions from routing or authentication never reach it and the client gets a bare 500 |
Expecting the pipeline to continue without calling next | A component that does not call next is terminal — everything registered after it silently never runs |
Putting UseAuthorization before UseAuthentication or before UseRouting | In the first case HttpContext.User is still empty; in the second there is no endpoint metadata — the policy inspects nothing |
Writing to the response after next once the headers have been sent | Response.HasStarted is already true — changing the status code or a header throws |
Adding registrations to builder.Services after Build() | The collection is frozen — InvalidOperationException; the service never reaches the container |
Injecting a Scoped DbContext into a Singleton | A captive dependency — one DbContext for the whole process: a bloated change tracker, the first request's state leaking into every later one, and concurrent use of a non-thread-safe object |
Resolving a Scoped service straight from the root provider in a background task | The instance lives until shutdown and is never disposed per request — you need IServiceScopeFactory |
Assuming appsettings.json always wins | Providers are stacked and the last one wins: an environment variable overrides the JSON, and command-line arguments override the environment variable |
Putting a secret in the base appsettings.json | The file is committed — the signing key or the database password ends up in the repository; secrets belong in user secrets and environment variables |
Expressing nesting in an environment-variable name with : | Such a name is invalid on some platforms — nesting is encoded with a double underscore __ |
| Probing the database in a liveness check | A transient database outage turns into a restart of every replica — dependencies belong in readiness |
| Treating liveness and readiness as the same check | A slow dependency restarts a healthy process instead of simply pulling it out of the load balancer |
Interview relevance
This topic comes up for everyone who claims backend C#, and almost always the same way: the interviewer gives you a concrete symptom and watches whether you have a model of the pipeline and the container, or only a memory of method names. "Why does UseAuthorization deny nothing?", "Why are the logs silent while the client sees a 500?", "Why does memory grow in production but not locally?" — all three answers live in the same plane: registration order and lifetime.
Typical checks:
- What a middleware component is and what calling
nextactually does — including the response phase on the way back up. - Why order is semantic, and what the canonical order is: exception handler → routing → authentication → authorization → endpoint.
- When the
IServiceProvideris built, where a request's services are resolved from, and who disposes them. - The difference between
Transient/Scoped/Singleton, and what a captive dependency is — the mechanism, not the definition. - Which configuration provider wins for a given key, and where a secret belongs.
- Why a containerized service exposes a health check, and how liveness differs from readiness by consequence.
Common wrong answer: "Lifetimes are just about how many objects get created; they do not change behavior." They do: a singleton capturing a scoped service does not create "one extra object" — it pins the reference once for the whole process. A candidate who follows that through to "and a DbContext is not thread-safe, so parallel requests get A second operation was started on this context instance" closes the lifetimes question outright.