# The ASP.NET Pipeline & Kestrel

> Learn what ASP.NET Core is actually built on: Kestrel the web server, the middleware pipeline and the request delegate, the host and its built-in dependency-injection container and configuration, and how minimal APIs and MVC are conveniences over endpoint routing. The plumbing under every .NET web app, made visible.


---

# The ASP.NET Pipeline & Kestrel

When you write `app.MapGet(...)` in ASP.NET Core, a lot happens before your code runs - and almost nobody
learns what. This is the **roots** guide for [ASP.NET Core](/guides/aspnet-core-from-zero): underneath the
minimal APIs and controllers sit a web server (**Kestrel**), a **middleware pipeline** that every request
flows through, and a **host** that wires up configuration and the dependency-injection container. Learn
these and the framework stops being a pile of conventions and becomes a small, legible machine.

The mental model is a server, a pipeline, and a host. **Kestrel** is the cross-platform web server that
actually listens on the socket and speaks HTTP. Each request it accepts runs through the **middleware
pipeline** - an ordered chain of functions, each a **`RequestDelegate`** that can do work, call the next
one, and act on the way back out (your endpoint is the end of the chain). And the **host** is the object
that builds it all: it reads configuration, sets up the DI container, and starts Kestrel. Hold "Kestrel
listens, the pipeline processes, the host wires it together," and every `app.Use(...)` and
`builder.Services...` line has an obvious home.

> 📝 This is a **roots** guide - it assumes **C#** ([C# From Zero](/guides/csharp-from-zero)) and is most
> rewarding after you've used [ASP.NET Core](/guides/aspnet-core-from-zero), so the pieces have somewhere
> to land. It's the .NET parallel to the [net/http roots guide](/guides/web-services-with-only-net-http)
> (Go) and [WSGI & ASGI](/guides/wsgi-and-asgi-explained) (Python). Examples run as .NET programs.

## How to read this

Short and foundational - read in order. It builds from "what Kestrel is" up through the pipeline, the
host, and how minimal APIs/MVC sit on top. Phases carry difficulty badges.

## The phases

1. **[What Kestrel & the Pipeline Are](01-what-kestrel-and-the-pipeline-are.md)** 🟢 - the server, the pipeline, and the host, and how a request flows.
2. **[Kestrel: The Web Server](02-kestrel-the-web-server.md)** 🟡 - the cross-platform server, the listener, and reverse proxies.
3. **[The Middleware Pipeline](03-the-middleware-pipeline.md)** 🟡 - `Use`/`Run`/`Map`, ordering, and short-circuiting.
4. **[The RequestDelegate](04-the-request-delegate.md)** 🔴 - what middleware really is: a function that wraps the next one.
5. **[The Host, DI & Configuration](05-host-di-configuration.md)** 🔴 - `WebApplication`/the generic host, the service container, and config sources.
6. **[How Minimal APIs & MVC Sit on Top](06-how-minimal-apis-and-mvc-sit-on-top.md)** 🟢 - endpoint routing, and where your handlers plug in.
7. **[Where to Go Next](07-where-to-go-next.md)** 🟢 - applying this to real apps, performance, and the ecosystem.

> The throughline: **Kestrel listens, a middleware pipeline of `RequestDelegate`s processes each request,
> and the host wires up DI + configuration + the server.** That's the machine inside every .NET web app.


---

# What Kestrel & the Pipeline Are

When you write `app.MapGet("/", () => "hi")` and a browser gets your text back, a surprising amount of
machinery ran before your lambda did. Someone opened a socket. Someone read raw bytes off the wire and
turned them into a request you could work with. Something carried that request through a chain of steps
and finally handed it to your handler. This guide opens that box.

This is the **roots** guide for [ASP.NET Core From Zero](/guides/aspnet-core-from-zero). Up there you
learn to *use* the framework; down here you learn what's underneath `MapGet`. It's the .NET parallel to
the Go [net/http roots guide](/guides/web-services-with-only-net-http) - same goal, different ecosystem:
make the foundation visible so the conveniences stop being magic.

> 📝 This is a roots guide. It assumes you know basic HTTP - methods, status codes, headers (see
> [HTTP, Explained](/guides/http-explained)) - and it lands best *after* you've built something with
> [ASP.NET Core](/guides/aspnet-core-from-zero), so the pieces have somewhere to attach. If you've never
> touched the framework, skim it now and come back; it'll click harder the second time.

## The mental model: three pieces, one sentence

Before any detail, get the shape in your head. Everything in ASP.NET Core's request handling is **three
moving parts**, and you can hold all of them in a single sentence:

💡 **Kestrel listens, the pipeline processes, the host wires it together.**

Carry that and every line in `Program.cs` has an obvious home. A request comes in off the network, Kestrel
turns it into something your code can read, the pipeline walks it through an ordered set of steps, your
endpoint produces a response, and the response flows back out the same pipeline to Kestrel and onto the
wire.

```mermaid
flowchart LR
  C[Client] -->|HTTP request| K[Kestrel]
  K -->|HttpContext| P[Middleware pipeline]
  P -->|in order| E[Your endpoint]
  E -->|response| P
  P -->|response| K
  K -->|HTTP response| C
```

*What just happened:* the client sends an HTTP request; **Kestrel** accepts the connection and packages
the request as an `HttpContext` (a tidy object holding the request, the response-in-progress, and shared
state). It hands that to the **pipeline**, an ordered chain of steps the request flows through. The last
step is **your endpoint**, which writes the response - and that response travels *back* through the same
chain on its way out. Notice the response retraces its steps: that round-trip is why middleware can act
both before and after your handler, which we'll lean on hard in later phases.

## The three pieces, one at a time

📝 **Kestrel** is the cross-platform web server built into ASP.NET Core. It's the part that actually opens
a socket, listens for connections, speaks the HTTP protocol, and reads raw bytes into an `HttpContext` it
can hand to your app. Every .NET web app has a real web server inside it - and it's Kestrel.

📝 **The middleware pipeline** is an ordered chain of functions that every request flows through. Each
function in the chain can inspect or change the request, decide whether to call the next function, and act
on the response on the way back out. Your endpoint sits at the *end* of that chain - it's the last thing
the request reaches.

📝 **The host** is the object that builds and runs everything. On startup it reads configuration (env
vars, `appsettings.json`, command-line args), sets up the dependency-injection container and logging, then
constructs the pipeline and starts Kestrel listening. It's the assembler that puts the other two pieces
together and presses start.

## Where these live in Program.cs

Here's the payoff. That familiar handful of lines at the top of every ASP.NET Core app isn't a magic
incantation - each line is *one of the three pieces*. Read it again with the model in hand:

```csharp
var builder = WebApplication.CreateBuilder(args);   // the host (config + DI)
var app = builder.Build();
app.Use(async (ctx, next) => { await next(ctx); }); // pipeline middleware
app.MapGet("/", () => "hi");                          // an endpoint (end of the pipeline)
app.Run();                                            // starts Kestrel listening
```

*What just happened:* line by line against the three pieces - 

- `WebApplication.CreateBuilder(args)` builds the **host**. This is where configuration sources get read
  and the DI container and logging get set up. The `builder.Build()` that follows finalizes it into a
  runnable `app`.
- `app.Use(...)` adds a step to the **pipeline**. The lambda gets the current `HttpContext` (`ctx`) and a
  `next` it can call to pass control down the chain. This one does nothing but forward - a placeholder for
  the real middleware you'll meet in Phase 3.
- `app.MapGet("/", () => "hi")` registers an **endpoint** - the end of the pipeline, the code that
  actually produces a response for `GET /`.
- `app.Run()` starts **Kestrel**. It binds to a port, begins the accept-loop, and blocks here, serving
  requests until the process is told to stop.

So the whole startup reads as one motion: *build the host, describe the pipeline, name your endpoints,
then let Kestrel listen.* Host, pipeline, server - in that order, every time.

> ⚠️ Don't confuse `app.Use` with `app.Run` here. `app.Use(...)` adds middleware to the pipeline;
> `app.Run()` (no lambda, called last) starts the server. There *is* also an `app.Run(handler)` overload
> that adds a terminal middleware - same name, different job - which is a classic early stumble. Phase 3
> untangles `Use`, `Run`, and `Map` properly; for now, just register that the bare `app.Run()` on the
> final line is "start Kestrel."

## What to expect from here

Be clear-eyed with yourself about what this guide is: it's **conceptual plumbing**. None of it changes what
your endpoints return - it explains the machine they run inside. That's exactly why it pays off most
*after* you've felt the framework's conveniences and started wondering what's beneath them. If "Kestrel
listens, the pipeline processes, the host wires it together" already gives those conveniences a place to
sit, the rest of this guide is just zooming in on each piece.

And that's the plan. Each of the three pieces gets its own phase: Kestrel the web server next, then the
middleware pipeline, then the `RequestDelegate` that middleware really is, then the host with its DI
container and configuration - and finally how minimal APIs and MVC are thin conveniences sitting on top of
all of it.

## Recap

1. Underneath `app.MapGet(...)` sit three pieces, captured in one sentence: **Kestrel listens, the
   pipeline processes, the host wires it together.**
2. **Kestrel** is the cross-platform web server built into ASP.NET Core - it opens the socket, speaks
   HTTP, and hands each request to your app as an `HttpContext`.
3. **The middleware pipeline** is an ordered chain of functions; each can act on the request, call the
   next, and act on the response on the way back out. Your endpoint is the end of the chain.
4. **The host** reads configuration, sets up the DI container and logging, builds the pipeline, and starts
   Kestrel.
5. `Program.cs` maps cleanly onto the three: `CreateBuilder(args)` = host, `app.Use(...)` = pipeline,
   `app.MapGet(...)` = endpoint, `app.Run()` = start Kestrel.
6. This is conceptual plumbing - most rewarding after you've used ASP.NET Core. Each piece gets its own
   phase from here.

## Quick check

Three questions on the model that has to stick before Phase 2:

```quiz
[
  {
    "q": "In one sentence, what is the ASP.NET Core mental model?",
    "choices": [
      "Kestrel listens, the pipeline processes, the host wires it together",
      "The host listens, Kestrel processes, the pipeline configures everything",
      "MVC routes the request and Kestrel writes the JSON response",
      "The pipeline opens the socket and the endpoint starts the server"
    ],
    "answer": 0,
    "explain": "Kestrel is the web server that listens and produces an HttpContext; the middleware pipeline is the ordered chain each request flows through; the host reads config, sets up DI, builds the pipeline, and starts Kestrel. Those three roles are the whole machine."
  },
  {
    "q": "What does Kestrel do in an ASP.NET Core app?",
    "choices": [
      "It listens on a socket, speaks HTTP, and hands each request to the app as an HttpContext",
      "It reads appsettings.json and configures the dependency-injection container",
      "It is the ordered chain of functions every request flows through",
      "It is the lambda you register with app.MapGet"
    ],
    "answer": 0,
    "explain": "Kestrel is the cross-platform web server built into ASP.NET Core. It accepts connections, reads raw bytes into an HttpContext, and hands that to the pipeline. Reading config and setting up DI is the host's job; the chain of functions is the pipeline."
  },
  {
    "q": "In Program.cs, which line starts Kestrel listening?",
    "choices": [
      "app.Run()",
      "WebApplication.CreateBuilder(args)",
      "app.Use(async (ctx, next) => { await next(ctx); })",
      "app.MapGet(\"/\", () => \"hi\")"
    ],
    "answer": 0,
    "explain": "The bare app.Run() on the final line starts the server - it binds a port, runs the accept-loop, and blocks. CreateBuilder builds the host, app.Use adds pipeline middleware, and app.MapGet registers an endpoint at the end of the pipeline."
  }
]
```


---

# Kestrel: The Web Server

**Kestrel is the program that owns the socket and speaks HTTP.** Nothing more mystical than that. When a browser opens a TCP connection to your app, Kestrel is the thing on the other end that accepts it, reads the raw bytes, figures out "this is an HTTP request for `GET /products`," and packages that up into an `HttpContext` object your code can work with. Then it hands that `HttpContext` to the pipeline (the chain you met in Phase 1) and waits for the response to come back so it can write the bytes out on the wire.

The detail that trips people up coming from the old .NET Framework world: **Kestrel runs in-process.** Your app *is* the server. There's no separate server product you install and configure that then "hosts" your DLL. You write `var app = builder.Build(); app.Run();` and that `app.Run()` call starts Kestrel right there inside your own process. The web server and your application code live in the same running program.

> 📝 If you came from ASP.NET on .NET Framework, this is the big shift. Back then **IIS** was the server - a separate Windows-only service that loaded your app. Today the server (Kestrel) is a library your app references and starts itself, and it runs on Windows, Linux, and macOS alike.

## What Kestrel actually handles

Owning the socket is a bigger job than it sounds. Kestrel is responsible for the messy, low-level networking work so your application code never has to think about it:

- **The protocols.** It speaks HTTP/1.1, HTTP/2, and HTTP/3 (QUIC). It negotiates which one to use per connection.
- **TLS.** It can terminate HTTPS - doing the certificate handshake and decrypting traffic - so by the time your code sees a request, it's already plaintext.
- **Connection management.** Keep-alives, timeouts, request size limits, concurrent connection limits, slow-client protection. Kestrel is the part that has to survive contact with the open internet.

Once all that's done, the result of each request is one tidy `HttpContext`, and that's the only thing your pipeline ever sees. Kestrel did the hard part; the pipeline does the interesting part.

```csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello from inside Kestrel");

app.Run();
```

*What just happened:* `WebApplication.CreateBuilder` set up a host that already includes Kestrel as the default server. `app.Run()` started Kestrel listening on a socket, and now every HTTP request that arrives gets turned into an `HttpContext` and routed to that `MapGet` handler. You never named Kestrel anywhere - it's the default, wired in for you.

## Telling Kestrel which ports to listen on

By default Kestrel needs to know one thing from you: where to listen. The URLs and ports come from **configuration**, and there are several sources, checked in a sensible order. From most common to most explicit:

- **`launchSettings.json`** - the dev-only file Visual Studio / `dotnet run` reads. This is where your `https://localhost:7001` style dev ports come from. It is *not* deployed to production.
- **The `ASPNETCORE_URLS` environment variable** - e.g. `ASPNETCORE_URLS=http://0.0.0.0:8080`. The standard way to set the port in containers and on servers.
- **The `--urls` command-line argument** - `dotnet run --urls "http://localhost:8080"`.
- **The `Kestrel` section of `appsettings.json`** - for richer config (endpoints, certificates, protocols) declaratively.
- **Code** - `builder.WebHost.ConfigureKestrel(...)` or `builder.WebHost.UseUrls(...)` when you want full control in C#.

```bash
# Set the listen URL via environment variable (great for containers)
export ASPNETCORE_URLS="http://0.0.0.0:8080"
dotnet run

# ...or pass it as an argument
dotnet run --urls "http://localhost:8080;https://localhost:8443"
```

*What just happened:* both forms tell Kestrel to bind to specific addresses and ports instead of the `launchSettings.json` defaults. `0.0.0.0` means "listen on all network interfaces" (you want this inside a container so traffic from outside can reach it); `localhost` means "only accept connections from this machine." The semicolon lets you list more than one endpoint.

If you'd rather configure it in code - say you need to tweak limits or bind programmatically:

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(8080); // HTTP on port 8080, all interfaces
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB cap
});

var app = builder.Build();
app.Run();
```

*What just happened:* `ConfigureKestrel` reached into Kestrel's own options and told it to listen on port 8080 and reject request bodies larger than 10 MB. This is the most explicit lever - useful when env vars and JSON aren't enough, but reach for it last, because hard-coding ports makes the app less portable across environments.

## In production: the reverse proxy question

In a lot of production setups, Kestrel doesn't face the internet alone. It sits behind a **reverse proxy** - nginx, Apache, IIS, YARP, or a cloud load balancer. Traffic hits the proxy first; the proxy then forwards it to Kestrel on an internal port.

Why bother? The proxy is a convenient place to put the operational concerns that aren't really your app's job: TLS termination, serving static files, load-balancing across several Kestrel instances, request buffering, and acting as a hardened front door. It shields Kestrel and lets ops teams use tooling they already know.

The catch: once a proxy sits in front, your app no longer sees the *real* client. The connection Kestrel sees comes from the proxy, so `HttpContext.Connection.RemoteIpAddress` is the proxy's IP, and the scheme might read as `http` even though the user came in over `https`. The proxy passes the originals along in `X-Forwarded-For` / `X-Forwarded-Proto` headers, and you tell ASP.NET Core to trust and apply them:

```csharp
var app = builder.Build();

app.UseForwardedHeaders(); // read X-Forwarded-* and fix up the request

app.MapGet("/whoami", (HttpContext ctx) =>
    $"You are {ctx.Connection.RemoteIpAddress}, scheme {ctx.Request.Scheme}");

app.Run();
```

*What just happened:* `UseForwardedHeaders` is middleware that reads the `X-Forwarded-For` and `X-Forwarded-Proto` headers the proxy set, then rewrites the request's remote IP and scheme to the real values. Without it, your logging, redirects, and any IP-based logic would all see the proxy instead of the actual user. (Configure which proxies you trust before enabling this in production - blindly trusting forwarded headers is a spoofing risk.)

> ⚠️ A reverse proxy is a **choice, not a rule.** Kestrel is hardened and fully supported facing the internet directly - plenty of production apps run exactly that way, especially in containerized and cloud-native setups. People put a proxy in front for TLS, static files, and operational convenience, not because Kestrel "can't handle it." Decide based on your ops needs, not folklore.

## The line Kestrel will not cross

One last thing to lock in, because it's the bridge to the next phase. 📝 **Kestrel knows nothing about routing or middleware.** It has no idea that `/products` maps to a particular handler, no concept of "authentication middleware runs before authorization." Its entire worldview is: accept a connection, parse HTTP, build an `HttpContext`, hand it off, write back the response.

Everything about *what to do* with a request - matching routes, running auth, calling your endpoint - happens in the **middleware pipeline**, which is exactly what Phase 3 is about. Kestrel is the doorman who lets the request in and shows it where the hallway starts. What happens down that hallway is someone else's job.

## Recap

- **Kestrel owns the socket and speaks HTTP** - it accepts TCP connections, parses requests, and hands each one to your pipeline as an `HttpContext`.
- It runs **in-process**: your app *is* the server, started by `app.Run()` - a deliberate break from the old IIS-hosted, Windows-only model.
- It handles the low-level work: **HTTP/1.1, HTTP/2, HTTP/3, TLS, and connection management**, so your code only ever sees a clean request.
- **Listen URLs come from configuration** - `launchSettings.json` (dev), `ASPNETCORE_URLS`, `--urls`, the `Kestrel` section of `appsettings.json`, or `ConfigureKestrel`/`UseUrls` in code.
- In production Kestrel often sits behind a **reverse proxy** (nginx/IIS/YARP/cloud LB) for TLS and ops convenience; add `UseForwardedHeaders` so the app sees the real client IP and scheme. But facing the internet directly is a fully supported choice.
- Kestrel knows **nothing about routing or middleware** - that's the pipeline's job, coming up in Phase 3.

## Quick check

```quiz
[
  {
    "q": "What does it mean that Kestrel runs 'in-process'?",
    "choices": ["A separate server product loads your compiled DLL", "Your application is the server process; app.Run() starts Kestrel inside it", "Kestrel runs as a Windows-only service", "Each request gets its own operating-system process"],
    "answer": 1,
    "explain": "Kestrel is a library your app references and starts itself with app.Run(), so the web server and your code live in the same running process."
  },
  {
    "q": "You deploy behind nginx and your app logs show every request coming from the same IP, with the scheme reading as http. What fixes it?",
    "choices": ["Switch Kestrel to HTTP/3", "Add app.UseForwardedHeaders() so the X-Forwarded-* headers are applied", "Hard-code the port with ConfigureKestrel", "Disable TLS termination on the proxy"],
    "answer": 1,
    "explain": "Behind a proxy the connection Kestrel sees is the proxy's. UseForwardedHeaders reads X-Forwarded-For / X-Forwarded-Proto and restores the real client IP and scheme."
  },
  {
    "q": "Which statement about a reverse proxy in front of Kestrel is correct?",
    "choices": ["Kestrel cannot safely face the internet, so a proxy is mandatory", "A proxy is a choice for TLS and ops convenience; Kestrel can also face the internet directly", "A proxy replaces the middleware pipeline", "Only IIS can act as a reverse proxy for Kestrel"],
    "answer": 1,
    "explain": "Kestrel is hardened and supported facing the internet directly. Teams add a proxy for TLS termination, static files, and load balancing - it's an operational choice, not a requirement."
  }
]
```


---

# The Middleware Pipeline

In Phase 2 we left Kestrel holding a fully-formed request, ready to hand it off. This is where it hands it off *to*.

**The pipeline is an ordered chain of middleware, and a request flows through it like an onion.** Each middleware gets the request on the way *in*, does some work, then calls `next` to pass control deeper. Eventually something at the center produces a response, and that response unwinds back *out* through every layer in reverse - each middleware getting a second turn on the way up. Kestrel feeds requests into the top of this onion; what comes back out is what the client sees.

That "in, then back out" shape is the thing to hold. A middleware isn't a one-shot handler that runs and disappears - it *wraps* everything registered after it. The first one you add is the outermost skin; the last is closest to the core.

```mermaid
flowchart LR
  K[Kestrel] --> A[Logging in]
  A --> B[Auth in]
  B --> C[Endpoint]
  C --> B2[Auth out]
  B2 --> A2[Logging out]
  A2 --> R[Response]
```

> 💡 The onion isn't a teaching metaphor the framework bolted on - under the hood it really is a stack of nested function calls, each holding a reference to the next as a `RequestDelegate`. We stay at the using-it level here; Phase 4 cracks one open to show you that function.

## The three building blocks: `Use`, `Run`, `Map`

You assemble the pipeline in `Program.cs` by adding middleware to your `WebApplication` (the `app` variable). There are three verbs that do almost all the work.

**`app.Use`** adds a middleware that *may* call the next one. It receives the request `context` and a `next` delegate. You do work, `await next(context)` to run the rest of the pipeline, then do more work after it returns. Both edges of the onion in one lambda.

**`app.Run`** adds a *terminal* middleware. It takes only `context` - no `next` - because it's the end of the line. Whatever it writes becomes the response, and nothing after it runs.

Here's a tiny but complete pipeline: a logging `Use` wrapping a terminal `Run`.

```csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    app.Logger.LogInformation("--> {Method} {Path}", context.Request.Method, context.Request.Path);
    await next(context);                          // run everything below us
    app.Logger.LogInformation("<-- {Status}", context.Response.StatusCode);
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from the bottom of the pipeline.");
});

app.Run();   // note: this Run() starts the host - different from app.Run(handler) above
```

*What just happened:* one request enters the logging middleware. The first log line fires on the way *in*. Then `await next(context)` hands control to the terminal `Run`, which writes the body - that *is* the center of the onion. Control comes back up to the logging middleware, the second log line fires on the way *out* (now that we know the status code), and the response leaves. One `Use` straddling both edges, one `Run` producing the response. (Watch the two meanings of `Run`: `app.Run(handler)` adds terminal middleware; the bare `app.Run()` on the last line is the host-start call from Phase 1. Same name, different jobs.)

**`app.Map`** branches the pipeline by request path. Anything matching the prefix peels off into its own sub-pipeline:

```csharp
app.Map("/admin", admin =>
{
    admin.Run(async context =>
        await context.Response.WriteAsync("Admin area"));
});
```

*What just happened:* requests starting with `/admin` enter the branch and run only what's configured inside it; everything else flows straight past, untouched. When you need to branch on something *other* than a path - a header, a query value, whether the request is HTTPS - reach for **`app.MapWhen(predicate, branch)`** (a fully separate branch) or **`app.UseWhen(predicate, branch)`** (runs the branch, then rejoins the main pipeline if it didn't short-circuit). `Map` is for "this whole slice of the app behaves differently."

## ⚠️ Order matters - a lot

This is where people get burned, so slow down here. **Middleware runs in the exact order you add it.** The first registration is the outermost layer; the last is nearest the endpoint. Swap two lines and you can silently break security or error handling - no exception, no warning, just wrong behavior.

The other half of that rule is **short-circuiting**: a middleware that calls `next` is a pass-through, but a middleware that **doesn't** call `next` ends the request right there and sends whatever response it has. That's not a bug - it's a primary tool. It's exactly how authentication rejects a bad request before the endpoint wastes a cycle on it, and how static-files serves a `.css` file and stops, never bothering routing or auth at all.

```csharp
app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("X-Api-Key"))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Missing API key.");
        return;                                  // <-- no next(): the pipeline stops here
    }

    await next(context);                          // key present - let it flow inward
});

app.Run(async context => await context.Response.WriteAsync("Protected payload."));
```

*What just happened:* when the header is missing we set a 401, write a message, and `return` without ever calling `next`. The request never reaches the terminal `Run` below - it short-circuited at the outer layer. When the header is present, `await next(context)` lets it continue. That fork, "reject now or pass it down," is the entire job of auth, caching, and rate-limiting middleware, just with real tokens instead of a header check. And it only works because this middleware sits *before* the thing it's protecting. Order is the law.

## Built-in middleware is packaged pipeline pieces

You rarely hand-write the security and routing layers - the framework ships them as `Use*` extension methods, each one a pre-built chunk of pipeline you drop in by calling it. `UseRouting` matches the request to an endpoint. `UseAuthentication` figures out *who* the caller is. `UseAuthorization` decides whether that who is *allowed*. `UseStaticFiles` serves files (and short-circuits when it finds one). `UseExceptionHandler` wraps everything below it to catch what they throw.

Because they're just middleware, the ordering law applies to them too - and their relative order isn't negotiable:

```csharp
app.UseExceptionHandler("/error");   // outermost - only an outer layer can catch inner errors
app.UseStaticFiles();                // serve a file and stop, before routing/auth even run
app.UseRouting();                    // decide WHICH endpoint matches
app.UseAuthentication();             // WHO is this? (reads tokens/cookies)
app.UseAuthorization();              // is this WHO allowed to hit that endpoint?

app.MapGet("/products", () => Results.Ok("listing products"));   // endpoints last
```

*What just happened:* each line is a packaged piece of the onion, and the sequence encodes real dependencies. The exception handler goes first so it surrounds everything. `UseRouting` runs before the auth pair because authorization can't read an endpoint's permission rules until routing has picked the endpoint. `UseAuthentication` always precedes `UseAuthorization` - you must know *who* before you can check *what they're allowed*. Endpoints come last, after auth has had its say. Move `UseAuthorization` below the `MapGet` and the endpoint runs before anyone checks permissions; your `[Authorize]` rules quietly do nothing.

> 📝 This is the **same pipeline** you used in [ASP.NET Core From Zero](/guides/aspnet-core-from-zero) - same `Use`/`Run`/`Map`, same ordering law. There you wired it up to build features; here you're looking at the mechanism itself. Phase 4 goes one level deeper and shows what a middleware *actually is* underneath: a `RequestDelegate` - a function that wraps the next function.

## Recap

- The pipeline is an **ordered onion of middleware**: Kestrel feeds the top, each layer runs on the way *in*, calls `next` to go deeper, and runs again on the way *out*.
- **`app.Use`** may call `next` to continue; **`app.Run`** is terminal and never calls `next`; **`app.Map`/`MapWhen`/`UseWhen`** branch the pipeline by path or condition.
- **Order is the law** - middleware runs in registration order, and getting it wrong fails silently rather than loudly.
- **Not calling `next` short-circuits** the request: it gets a response immediately and never reaches the endpoint. That's how auth rejects and static-files serves early.
- Built-in middleware (`UseRouting`, `UseAuthentication`, `UseAuthorization`, `UseStaticFiles`, `UseExceptionHandler`) are **packaged pipeline pieces**, and their relative order carries real dependencies.

## Quick check

```quiz
[
  {
    "q": "What is the difference between app.Use and app.Run?",
    "choices": [
      "Use is for GET requests; Run is for POST requests",
      "Use may call next to continue the pipeline; Run is terminal and never calls next",
      "Use runs in development; Run runs in production",
      "There is no difference - they are aliases"
    ],
    "answer": 1,
    "explain": "Use receives a next delegate and can pass control deeper; Run takes only context, ends the chain, and always produces the response itself."
  },
  {
    "q": "A middleware sets a 401 and returns WITHOUT calling next. What happens to the request?",
    "choices": [
      "It still reaches the endpoint, which overrides the 401",
      "ASP.NET Core throws because next is required",
      "The pipeline short-circuits - the 401 is sent and inner middleware/endpoints never run",
      "The request restarts from the top of the pipeline"
    ],
    "answer": 2,
    "explain": "Not calling next short-circuits the pipeline: the response is sent immediately and nothing registered after it runs. This is exactly how auth rejects early."
  },
  {
    "q": "Why must UseRouting be registered before UseAuthorization?",
    "choices": [
      "Alphabetical order is required by the compiler",
      "Authorization needs the matched endpoint to read its permission rules, and routing is what matches it",
      "Routing only works after authorization has run",
      "It doesn't matter - order is ignored for built-in middleware"
    ],
    "answer": 1,
    "explain": "Routing selects which endpoint the request hit; authorization then reads that endpoint's [Authorize] rules. Without routing first, there's no endpoint to authorize against."
  }
]
```


---

# The RequestDelegate

There is exactly **one type** at the bottom of the whole pipeline, and once you see it, every
`app.Use(...)`, every middleware class, every `MapGet` stops being a separate concept and becomes
the same shape wearing different clothes.

The shape is this:

- A **`RequestDelegate`** is **a function from `HttpContext` to a `Task`** - "give me a
  request, I'll handle it and hand you back a `Task` for when I'm done."
- A **middleware** is **a function that takes the *next* `RequestDelegate` and returns a *new*
  `RequestDelegate`** - it wraps the rest of the pipeline so it can do work before and after.

And the punchline: **your entire app is ultimately one `RequestDelegate`.** All your middleware,
composed together, collapse into a single function that Kestrel hands each `HttpContext` to.
Hold those two sentences and the rest of this phase is detail.

> 📝 This is the deepest phase in the guide. It assumes you're comfortable with C# delegates,
> `async`/`await`, and the DI container ([C# From Zero](/guides/csharp-from-zero) and the
> previous two phases of this guide cover the ground you need). If `Use`/`Run`/`Map` aren't
> familiar yet, read [Phase 3](03-the-middleware-pipeline.md) first.

## The atom: `RequestDelegate`

The type itself is almost anticlimactic:

```csharp
public delegate Task RequestDelegate(HttpContext context);
```

*What just happened:* We named a delegate type. A `RequestDelegate` is any method (or lambda)
that takes one `HttpContext` and returns a `Task`. That's the entire contract - no return value
beyond the `Task`, because the "response" isn't returned, it's written *into* `context.Response`.
Your endpoint handler is a `RequestDelegate`. The thing Kestrel invokes per request is a
`RequestDelegate`. It is the atom the whole pipeline is built from.

## `app.Use` is sugar for `Func<RequestDelegate, RequestDelegate>`

Now the second half. When you write inline middleware with `app.Use`, what you're really
describing is a **function that wraps the next delegate** - conceptually a
`Func<RequestDelegate, RequestDelegate>`. It receives `next` (everything registered after it,
already composed into one delegate) and returns a brand-new `RequestDelegate` that does some
work, calls `next`, and does more work on the way back.

Written out without the `app.Use` sugar, a logging middleware looks like this:

```csharp
// app.Use(...) is sugar for composing RequestDelegates. Conceptually:
RequestDelegate Logging(RequestDelegate next) => async context =>
{
    var start = DateTime.UtcNow;
    await next(context);                 // call the rest of the pipeline
    var ms = (DateTime.UtcNow - start).TotalMilliseconds;
    context.RequestServices.GetRequiredService<ILogger<Program>>()
        .LogInformation("{Path} {Status} {Ms}ms", context.Request.Path, context.Response.StatusCode, ms);
};
```

*What just happened:* `Logging` is a function that takes `next` (a `RequestDelegate`) and
returns a new `RequestDelegate` - the `async context => { ... }` lambda. Inside that lambda we
do work *before* (`start`), then `await next(context)` to run the rest of the pipeline, then do
work *after* (compute `ms`, log). The "before" and "after" sit on either side of the one
`await next` call. That symmetry - code before, call `next`, code after - is the whole story of
middleware, and it's exactly what `app.Use(async (context, next) => { ... await next(context); ... })`
compiles down to. The framework just spares you from naming the wrapping function.

> 💡 Notice where `next` comes from: it's already the *rest* of the pipeline, pre-composed into
> a single `RequestDelegate`. Each middleware only ever sees "me and everything after me as one
> function." It never needs to know how many middlewares follow or what they are.

## Convention-based middleware classes

Inline lambdas are fine for two-line concerns, but real middleware usually wants a class - its
own file, constructor, testability. ASP.NET Core supports a **convention-based** class: no
interface to implement, no base class to inherit. You just follow a shape:

```csharp
public class LoggingMiddleware
{
    private readonly RequestDelegate _next;

    public LoggingMiddleware(RequestDelegate next)   // the "next" delegate, captured once
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, ILogger<LoggingMiddleware> logger)
    {
        var start = DateTime.UtcNow;
        await _next(context);
        var ms = (DateTime.UtcNow - start).TotalMilliseconds;
        logger.LogInformation("{Path} {Status} {Ms}ms",
            context.Request.Path, context.Response.StatusCode, ms);
    }
}

// register it:
app.UseMiddleware<LoggingMiddleware>();
```

*What just happened:* This is the same logging middleware as before, restructured. The
constructor takes `RequestDelegate next` and stashes it - that's the "wrap the next delegate"
part. The `InvokeAsync` method is the new `RequestDelegate` body: it gets the `HttpContext`,
does before/after work around `await _next(context)`. `app.UseMiddleware<LoggingMiddleware>()`
recognizes the convention (constructor-takes-`next`, has `InvokeAsync(HttpContext, ...)`) and
slots it into the pipeline. Note `ILogger` arrives as a *parameter of `InvokeAsync`*, not the
constructor - and that detail is not cosmetic.

> ⚠️ **The captive-dependency trap.** A convention-based middleware instance is constructed
> **once**, for the lifetime of the app - effectively a singleton. So anything you inject into
> the **constructor** is also captured once and reused for every request forever. If you inject
> a **Scoped** service (a `DbContext`, a per-request unit-of-work) into the constructor, you've
> captured one request's instance and frozen it across all future requests - a "captive
> dependency," and a genuinely nasty bug (stale data, cross-request leakage, disposed-object
> exceptions). The fix is the rule above: **inject per-request (Scoped) services as parameters
> of `InvokeAsync`**, which the framework resolves fresh from the request's scope on every call.
> Constructor injection is only safe for **Singleton** services. (More on service lifetimes in
> [Phase 5](05-host-di-configuration.md).)

## Composition order: last registered is innermost

So how do the pieces become one function? When the app builds, the framework composes the
middlewares **from the last registered to the first**. Each one's `Func<RequestDelegate, RequestDelegate>`
is handed the already-composed delegate of everything after it. The result: the **first** `Use`
you wrote becomes the **outermost** wrapper, and the last becomes the innermost (sitting right
next to your endpoint).

```csharp
app.Use(/* A */ ...);   // outermost: runs first on the way in, last on the way out
app.Use(/* B */ ...);
app.Use(/* C */ ...);   // innermost: closest to the endpoint
app.Run(/* endpoint */);
```

*What just happened:* Reading top-to-bottom is the order requests *enter*: A, then B, then C,
then the endpoint. Because each middleware does work *after* `await next`, the way *out* is the
mirror image: endpoint, then C, then B, then A. Composition built this nesting by wrapping
inside-out - `A(B(C(endpoint)))` - which is why registration order is the single most important
thing about a pipeline (exactly the lesson from [Phase 3](03-the-middleware-pipeline.md), now
explained from the inside).

> 💡 If this "a function that takes `next` and returns a new function" shape feels familiar,
> it's because it's the *universal* pattern for middleware, not a .NET invention. Go's idiom is
> literally `func(next http.Handler) http.Handler` - same signature, same wrapping. And Rust's
> tower expresses it as a `Layer` that wraps a `Service` to produce a new `Service` - the
> parallel is exact, down to "compose inside-out so the first layer is outermost." If you've
> internalized one, you've internalized all three. See
> [hyper & tower](/guides/hyper-and-tower) for the Rust telling of the very same idea.

## Recap

- A **`RequestDelegate`** is `Task RequestDelegate(HttpContext context)` - a function from a
  request to a `Task`. Your endpoint is one; the whole pipeline collapses into one.
- A **middleware** is a function that takes the **next** `RequestDelegate` and returns a **new**
  one - work before, `await next(context)`, work after. `app.Use` is sugar for exactly this
  (`Func<RequestDelegate, RequestDelegate>`).
- **Convention-based middleware classes** take `RequestDelegate next` in the constructor and
  expose `public async Task InvokeAsync(HttpContext context, ...)`, registered with
  `app.UseMiddleware<T>()`.
- ⚠️ The instance is created **once**. Inject **Scoped** services as `InvokeAsync` parameters,
  never the constructor - constructor injection of Scoped services is the **captive-dependency**
  bug.
- The pipeline composes **last-registered-innermost**, so the **first** `Use` is the
  **outermost** call - which is why ordering decides everything.
- It's the same middleware shape as Go's `func(next) handler` and Rust tower's `Layer`/`Service`.

## Quick check

```quiz
[
  {
    "q": "What is a RequestDelegate?",
    "choices": ["A class you inherit from to make middleware", "A function from HttpContext to a Task - the atom the pipeline is built from", "A DI lifetime like Scoped or Singleton", "The Kestrel socket listener"],
    "answer": 1,
    "explain": "RequestDelegate is `Task RequestDelegate(HttpContext context)` - a function that handles a request and returns a Task. Middleware and endpoints are all this shape, and the whole app composes into one."
  },
  {
    "q": "Conceptually, what is a middleware?",
    "choices": ["A function that takes the next RequestDelegate and returns a new RequestDelegate", "A subclass of HttpContext", "A method that returns the response object directly", "A configuration source"],
    "answer": 0,
    "explain": "Middleware is a Func<RequestDelegate, RequestDelegate>: it receives `next` (the rest of the pipeline) and returns a new delegate that does work, awaits next, and does more work on the way out. app.Use is sugar for this."
  },
  {
    "q": "Why inject a Scoped service into InvokeAsync rather than the middleware constructor?",
    "choices": ["Constructor injection is slower", "The middleware instance is created once, so a constructor-injected Scoped service becomes a captive dependency reused across all requests", "InvokeAsync can't access the constructor", "Scoped services aren't registered until InvokeAsync runs"],
    "answer": 1,
    "explain": "A convention-based middleware is instantiated once (singleton-like). A Scoped service captured in its constructor is frozen for the app's lifetime - the captive-dependency bug. InvokeAsync parameters are resolved fresh from each request's scope."
  }
]
```


---

# The Host, DI & Configuration

**Before your app exists, something has to assemble configuration, the DI container, logging, and Kestrel - and that something is the host.** You've spent four phases watching a request flow through Kestrel and the pipeline. This phase is about the machinery that *stands all of that up* in the first place, then owns its lifecycle from start to shutdown.

The shape of every ASP.NET Core program is the same three-beat rhythm: `CreateBuilder` → configure the builder → `Build()` → `Run()`. Once you see those four lines as "set up the wiring, then run the machine," the top of `Program.cs` stops being boilerplate you copy and becomes a place you actually understand.

```csharp
var builder = WebApplication.CreateBuilder(args);  // 1. set up config, DI, logging, Kestrel
builder.Services.AddScoped<IProductRepository, ProductRepository>();  // 2. register
var app = builder.Build();  // 3. produce the running app (the host)
// ... configure the pipeline ...
app.Run();  // 4. start Kestrel and block until shutdown
```

*What just happened:* `CreateBuilder` returned a **`WebApplicationBuilder`** - a setup object that has *already* prepared four things before you touch it: configuration, the DI container, logging, and Kestrel. You then add to that setup (registering services, reading config). `Build()` turns the builder into a **`WebApplication`** - the host, the thing that runs. `Run()` starts Kestrel listening and blocks the main thread until the app is told to shut down. Builder phase, then run phase. Nothing happens to requests until `Run()`.

> 📝 "The host" is just the name for the object that builds, owns, and runs your app. The `WebApplication` you get from `Build()` is unusually multi-talented: it implements the **host lifecycle** (start/stop), the **pipeline builder** (`app.Use(...)` from Phase 3), and the **endpoint route builder** (`app.MapGet(...)`, coming in Phase 6) all in one. That's why a single `app` variable does so much.

## The DI container: `builder.Services`

The first of the three things the builder sets up is the **DI container**. `builder.Services` is an `IServiceCollection` - a registration sheet. You add rules to it ("when something asks for `IProductRepository`, give it a `ProductRepository`"), and after `Build()`, the finished container is what resolves your middleware and your endpoints.

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddSingleton<IClock, SystemClock>();

var app = builder.Build();   // the registration sheet becomes a real container here
```

*What just happened:* Every `builder.Services.Add...` call writes one line onto the registration sheet. Those calls do nothing on their own - `Build()` is the moment the sheet is "frozen" into an actual service provider that can construct objects. After that point, when Kestrel hands a request to the pipeline, the container is what builds the middleware and the endpoint handlers, supplying each its declared dependencies.

The detail that ties this phase to request handling is **scopes**. The container creates a fresh **scope** for each incoming request, and `HttpContext.RequestServices` *is* that per-request provider. This is the actual mechanism behind "Scoped = one instance per request": a scoped service is cached inside the request's scope and disposed when the request ends.

```csharp
app.Use(async (context, next) =>
{
    // This resolves from the CURRENT request's scope, not a global one.
    var repo = context.RequestServices.GetRequiredService<IProductRepository>();
    // ... do something with repo for just this request ...
    await next(context);
});
```

*What just happened:* `context.RequestServices` is the scope the container opened for *this* request. Resolving `IProductRepository` from it gives you the one instance shared across this request and no other. Two simultaneous requests get two separate scopes, hence two separate scoped repositories - which is exactly why scoped is the right default for a `DbContext`. The full treatment of lifetimes, constructor injection, and the captive-dependency trap lives in [Dependency Injection](/guides/aspnet-core-from-zero) from the ASP.NET Core guide; here the point is *where* the container comes from and *when* the scope is created.

## Configuration: layered providers

The second thing the builder sets up is **configuration**. `builder.Configuration` is an **`IConfiguration`** - a single read-through view assembled from several **providers** stacked in order, where **later providers override earlier ones** for the same key. The default stack, from lowest to highest priority:

1. `appsettings.json`
2. `appsettings.{Environment}.json` (e.g. `appsettings.Production.json`)
3. User secrets (development only)
4. Environment variables
5. Command-line args

```csharp
var builder = WebApplication.CreateBuilder(args);

string greeting = builder.Configuration["Greeting"]
                  ?? "Hello";
string? connString = builder.Configuration.GetConnectionString("Default");
```

*What just happened:* `builder.Configuration["Greeting"]` walks the merged view and returns whatever the *highest-priority* provider set for `"Greeting"`. If `appsettings.json` says `"Hello"` but an environment variable `Greeting=Hi` is present, you get `"Hi"` - the env var won because it sits higher in the stack. `GetConnectionString("Default")` is sugar for reading `ConnectionStrings:Default`. The colon `:` is how you reach into nested JSON sections.

The override order is the whole point. It's what lets you commit safe defaults to `appsettings.json`, layer environment-specific values in `appsettings.Production.json`, keep real secrets out of source control via user secrets locally, and override anything at deploy time with an environment variable - without changing code.

> ⚠️ Reading config by raw string key everywhere (`Configuration["Smtp:Host"]` scattered across ten files) gets fragile fast: typos fail silently, returning `null`, and there's no one place that documents what settings exist. The fix is the options pattern, next.

### The options pattern: bind a section to a typed class

Instead of reading loose strings, bind a configuration **section** to a strongly-typed class once, then inject that class wherever you need it.

Given this in `appsettings.json`:

```json
{
  "Smtp": {
    "Host": "mail.example.com",
    "Port": 587
  }
}
```

You define a matching class, bind it during setup, and inject `IOptions<T>`:

```csharp
public class SmtpSettings
{
    public string Host { get; set; } = "";
    public int Port { get; set; }
}

// In Program.cs, before Build():
builder.Services.Configure<SmtpSettings>(
    builder.Configuration.GetSection("Smtp"));

// Anywhere a service is constructed:
public class EmailSender
{
    private readonly SmtpSettings _settings;
    public EmailSender(IOptions<SmtpSettings> options)
    {
        _settings = options.Value;   // .Value unwraps the bound settings
    }
}
```

*What just happened:* `Configure<SmtpSettings>` tells the container to read the `"Smtp"` section and map its keys onto the properties of `SmtpSettings` by name (`Host` → `Smtp:Host`, `Port` → `Smtp:Port`). Then any class can declare `IOptions<SmtpSettings>` in its constructor and the container supplies it, fully populated. You read typed, documented properties (`_settings.Port` is an `int`, not a string you have to parse) instead of stringly-typed keys, and the *names* of your settings now live in one C# class. Cleaner, type-safe, and discoverable.

## Environments: dev vs. prod

The builder also exposes the **environment** - which named environment the app is running as. `builder.Environment` is an `IHostEnvironment`, and its value comes from the **`ASPNETCORE_ENVIRONMENT`** environment variable (defaulting to `Production` if unset). This is the same name that selects `appsettings.{Environment}.json` above.

```csharp
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();   // detailed errors - dev only
}
else
{
    app.UseExceptionHandler("/error"); // friendly page in production
}
```

*What just happened:* `IsDevelopment()` checks whether `ASPNETCORE_ENVIRONMENT` is `"Development"`. Setting that variable to `Development` on your machine and leaving it as `Production` on the server lets the *same code* show stack traces locally and a clean error page in production. `IsProduction()` and the generic `IsEnvironment("Staging")` work the same way. The environment is a deploy-time switch, not a code change - exactly like configuration.

## The generic host runs more than web apps

> 📝 Underneath `WebApplication` sits the **generic host**, and it isn't web-specific. It can run non-web apps too - and even in a web app, you can register background work that runs *alongside* Kestrel. You do that with an `IHostedService` (or the simpler `BackgroundService` base class), and the host starts it on startup and stops it on shutdown.

```csharp
public class QueueCleaner : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // ... drain a queue, run a timer, sweep stale data ...
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}

// Register it like any other service:
builder.Services.AddHostedService<QueueCleaner>();
```

*What just happened:* `BackgroundService` is a hosted service with one method to fill in, `ExecuteAsync`. The host calls it once at startup and passes a `CancellationToken` that trips when the app is shutting down - so your loop exits cleanly. Registered via `AddHostedService`, `QueueCleaner` now runs for the life of the app, in parallel with request handling, sharing the same DI container and configuration. Timers, queue consumers, periodic cleanup - this is where they live. (Drop the web parts entirely and the generic host happily runs a console worker service with no Kestrel at all.)

## Recap

- **The host wires up four things before your app runs** - configuration, the DI container, logging, and Kestrel - then owns the lifecycle. The rhythm is `CreateBuilder` → configure → `Build()` → `Run()`.
- **`CreateBuilder` returns a `WebApplicationBuilder`; `Build()` returns a `WebApplication`** that is the host, the pipeline builder, and the endpoint route builder in one object.
- **The DI container** is `builder.Services` (an `IServiceCollection`); after `Build()` it resolves middleware and endpoints, opening a fresh **scope** per request - `HttpContext.RequestServices` is that per-request provider.
- **Configuration** is layered: `appsettings.json` → `appsettings.{Environment}.json` → user secrets → environment variables → command-line args, later overriding earlier. Read with `Configuration["Key"]` / `GetConnectionString(...)`, or bind a section with the **options pattern** (`Configure<T>` + `IOptions<T>`).
- **The environment** comes from `ASPNETCORE_ENVIRONMENT` and is exposed via `builder.Environment` (`IsDevelopment()` / `IsProduction()`) - a deploy-time switch, not a code change.
- **The generic host underneath also runs non-web work**: register an `IHostedService` / `BackgroundService` for timers, queue consumers, and other background tasks that run alongside (or without) the web server.

## Quick check

```quiz
[
  {
    "q": "What does WebApplicationBuilder set up before your app exists?",
    "choices": ["Only the middleware pipeline", "Configuration, the DI container, logging, and Kestrel", "Only the routing table", "Just the connection string"],
    "answer": 1,
    "explain": "CreateBuilder returns a WebApplicationBuilder that has already prepared configuration, the DI container, logging, and Kestrel before you add anything to it."
  },
  {
    "q": "Two configuration providers both set the key \"Greeting\". Which value wins?",
    "choices": ["The one from the provider added earliest", "The one from the provider higher in the stack (added later)", "It throws because of the conflict", "appsettings.json always wins"],
    "answer": 1,
    "explain": "Providers are layered and later ones override earlier ones. With the default stack, an environment variable beats appsettings.json for the same key."
  },
  {
    "q": "You need a timer that drains a queue every five minutes alongside your web app. What do you register?",
    "choices": ["A Singleton middleware", "An IOptions<T> binding", "An IHostedService / BackgroundService via AddHostedService", "A new Kestrel listener"],
    "answer": 2,
    "explain": "The generic host runs background work registered as an IHostedService (or BackgroundService) with AddHostedService - it starts on startup and stops cleanly on shutdown."
  }
]
```


---

# How Minimal APIs & MVC Sit on Top

The whole payoff of this guide in one sentence: **your handlers - minimal API delegates and MVC action methods alike - are *endpoints*, and the pipeline's entire job is to route a request to one endpoint and run it.** Everything you learned the framework "does for you" is plumbing you've now seen from the inside. `MapGet` doesn't do anything mystical; it adds an endpoint to a table. The pipeline you studied in the last phases is what finds that endpoint and executes it.

For the last five phases we've been building from the metal up: Kestrel listens on the socket, a chain of `RequestDelegate`s processes each request, and the host wires up DI and configuration. This phase closes the loop by showing where *your* code - the `MapGet` lambdas and `[ApiController]` classes you write every day - plugs into that machine. They're not a separate world bolted on top. They're the last stop on the same pipeline.

> 📝 This phase is the bridge back to everyday ASP.NET Core. If you've used minimal APIs from [ASP.NET Core From Zero](/guides/aspnet-core-from-zero), you already *use* endpoints - here you'll see what an endpoint actually *is* and why middleware ordering shakes out the way it does.

## Endpoint routing: match, then execute

The thing that trips people up is that routing in ASP.NET Core is **two pipeline stages, not one**, and they sit at opposite ends of the pipeline. Hold that and the rest is obvious.

The first stage is **`UseRouting`**. It runs early. Its job is to look at the incoming request - method and path - and **match** it against the registered endpoints, picking which handler *will* run. It does not run your code. It just decides "this request belongs to the `GET /products` endpoint" and stashes that decision (plus the endpoint's metadata, like any `[Authorize]` attribute) on the `HttpContext` so later middleware can read it.

The second stage is the **endpoint middleware** at the very *end* of the pipeline (added by `UseEndpoints` in older setups, or implicitly for you in modern minimal hosting). Its job is to **execute** the endpoint that `UseRouting` matched - bind parameters, run your delegate or action, and write the response.

```csharp
var app = builder.Build();

app.UseRouting();        // STAGE 1: match - which endpoint will run?
app.UseAuthorization();  // in between: now we KNOW the endpoint, so we can check its [Authorize]
// ... endpoint execution happens at the end (added implicitly) - STAGE 2: run it

app.MapGet("/products", () => "all products");

app.Run();
```

*What just happened:* `UseRouting` matches the request to the `GET /products` endpoint near the front of the pipeline, but the endpoint isn't actually *run* until the terminal stage at the back. Between those two points, the request is still flowing through middleware - and that gap is where the next piece clicks into place.

> ⚠️ This is *the* reason `UseAuthorization` goes **between** `UseRouting` and the endpoint, and getting that order wrong is a classic bug. Authorization needs to know *which* endpoint was matched - and read its `[Authorize]` metadata - before it can decide whether to let the request through. Put `UseAuthorization` before `UseRouting` and there's no matched endpoint yet, so it has nothing to check. The match-then-execute split is what makes the ordering rule make sense instead of being a magic incantation you memorize.

Here's the flow end to end:

```mermaid
flowchart LR
  A[Request from Kestrel] --> B[UseRouting: match endpoint]
  B --> C[UseAuthentication]
  C --> D[UseAuthorization: read matched endpoint metadata]
  D --> E[Endpoint middleware: execute the handler]
  E --> F[Response back out]
```

*What just happened:* The diagram makes the split visual. Matching happens up front (B), the matched endpoint's metadata is available to the auth middleware in the middle (D), and the actual handler doesn't run until the end (E). One pipeline, two routing stages, your code at the tail.

## Minimal APIs: `MapGet` registers an endpoint

Now map this onto code you've written. When you call `app.MapGet("/products", handler)`, you are doing exactly one thing: **adding an endpoint to the routing table**, whose handler is your delegate. That's it. `MapGet` is "register an endpoint." `UseRouting` later matches a request to it, and the endpoint middleware later runs your delegate as the pipeline's terminal stage.

```csharp
app.MapGet("/products/{id:int}", (int id) =>
{
    return Results.Ok(new { id, name = "Keyboard" });
});
```

*What just happened:* This line adds one endpoint to the table. Everything that feels automatic - the framework reading `id` out of the path and handing it to your lambda, and `Results.Ok(...)` turning into a `200` with a JSON body - are **conveniences the framework wraps around writing to `HttpContext` directly**. Parameter binding is the framework inspecting your delegate's parameters and filling them from the request. `IResult`/`Results` is a tidy object that knows how to write itself to the response. Strip the conveniences away and the endpoint is doing what any terminal middleware does: reading the request and writing the response. The request reaches your delegate as the *end* of the pipeline you've been studying.

> 💡 If the binding and `Results` mechanics feel hazy, that's the consumer's view - and it's covered hands-on in [ASP.NET Core From Zero](/guides/aspnet-core-from-zero). This guide's contribution is showing you the seam underneath: a `Map*` call is a table entry, and the table is consulted by `UseRouting`.

## MVC controllers: also endpoints, same system

Controllers feel like a different beast - classes, attributes, conventions - but on the pipeline they are **the same thing**: endpoints in the same routing system. You opt in with two lines:

```csharp
builder.Services.AddControllers();   // register the services controllers need
// ...
app.MapControllers();                // discover controllers, add them as endpoints
```

*What just happened:* `AddControllers()` registers the MVC services into the DI container you met in Phase 5. `MapControllers()` tells the framework to **discover your controller classes** and add each action as an endpoint in the *same* routing table that `MapGet` writes to. At request time, `UseRouting` matches the request to a controller-action endpoint just like any other, and the endpoint middleware runs it - doing **model binding, running filters, and invoking the matched action method**. The extra ceremony (filters, conventions, `[ApiController]` behaviors) is work the endpoint does when it executes; it's not a separate pipeline.

Because they share one routing system, minimal APIs and MVC **coexist on a single pipeline** without conflict:

```csharp
app.MapControllers();                       // controller endpoints
app.MapGet("/health", () => "ok");          // a minimal API endpoint, same table

app.Run();
```

*What just happened:* `MapControllers` and `MapGet` both add entries to the same endpoint table, so a request can route to a controller action or a minimal API delegate depending on what matches. They're two ways to describe an endpoint, not two competing frameworks. You can adopt minimal APIs incrementally in an MVC app, or sprinkle a controller into a minimal-API app, precisely because there's only one routing system underneath.

## Why minimal hosting hides all this - and why you should still know it

In modern ASP.NET Core, `WebApplication` (the thing `builder.Build()` returns) is helpful to a fault: it adds **`UseRouting` and the endpoint-execution stage for you** automatically, so most apps never call `UseRouting`/`UseEndpoints` explicitly. You write `MapGet` and `MapControllers`, never touch routing setup, and it works.

```csharp
var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();   // works correctly - routing is wired implicitly around it
app.MapControllers();

app.Run();
```

*What just happened:* There's no `UseRouting` line here, yet `UseAuthorization` still lands between matching and execution and behaves correctly. `WebApplication` placed the routing stage at the front and the endpoint stage at the back on your behalf, slotting your explicit middleware into the gap. Convenient - but invisible.

> 💡 Knowing the split is still worth it, because **the split is what explains middleware order**. The day you wonder "why does auth go after `UseRouting`?" or "why doesn't my middleware see which endpoint matched?", the answer is the two-stage model: match up front, execute at the back, metadata available in between. The framework hid the wiring, not the rules.

## Recap

- **Your handlers are endpoints.** Minimal API delegates and MVC actions are both endpoints; the pipeline's job is to route to one and run it.
- **Endpoint routing is two stages.** `UseRouting` *matches* the request to an endpoint (and exposes its metadata) near the front; the endpoint middleware *executes* it at the very end of the pipeline.
- **That split explains ordering.** `UseAuthorization` sits between them because it must read the matched endpoint's `[Authorize]` metadata before deciding - there's no endpoint to check before `UseRouting` runs.
- **`MapGet` adds an endpoint to the table.** Parameter binding and `Results`/`IResult` are conveniences over writing to `HttpContext`; the request reaches your delegate as the pipeline's terminal stage.
- **Controllers are endpoints too.** `AddControllers` + `MapControllers` discover controller classes and run binding/filters/the action as endpoints in the same routing system - so minimal APIs and MVC coexist on one pipeline.
- **Minimal hosting wires routing implicitly.** `WebApplication` adds `UseRouting`/endpoint execution for you, so you rarely call them - but the two-stage model is still what governs middleware order.

## Quick check

```quiz
[
  {
    "q": "Why does UseAuthorization need to sit between UseRouting and the endpoint execution stage?",
    "choices": ["Authorization is slow, so it runs in the middle to balance the pipeline", "UseRouting matches the endpoint and exposes its metadata, so UseAuthorization can read the endpoint's [Authorize] attribute before deciding", "It is purely a style convention with no functional reason", "UseAuthorization must run before any endpoint is matched so it can match faster"],
    "answer": 1,
    "explain": "Routing is split into match (UseRouting) and execute (endpoint middleware). Authorization needs the matched endpoint's metadata, which only exists after UseRouting runs - so it goes in between."
  },
  {
    "q": "On the pipeline, what does app.MapGet(\"/products\", handler) actually do?",
    "choices": ["It runs the handler immediately when the line executes", "It registers an endpoint in the routing table; the request reaches the handler as the terminal stage of the pipeline", "It adds a new piece of middleware that runs on every request", "It starts Kestrel listening on the /products path"],
    "answer": 1,
    "explain": "MapGet adds an endpoint to the table. UseRouting later matches a request to it, and the endpoint middleware runs your delegate as the pipeline's final stage. Binding and Results are conveniences over HttpContext."
  },
  {
    "q": "How do MVC controllers relate to minimal API endpoints on the pipeline?",
    "choices": ["Controllers run on a completely separate pipeline from minimal APIs", "Controllers bypass routing and are invoked directly by Kestrel", "AddControllers + MapControllers register controller actions as endpoints in the same routing system, so they coexist with minimal APIs on one pipeline", "Controllers must be converted to minimal APIs before they can run"],
    "answer": 2,
    "explain": "MapControllers discovers controller classes and adds their actions as endpoints in the same routing table MapGet writes to. They are the same kind of thing - endpoints - and share one pipeline."
  }
]
```


---

# Where to Go Next

Notice what changed. When you started this guide, a `Program.cs` was a wall of incantations - `builder.Services.Add...`, `app.Use...`, `app.MapGet(...)` - that you copied from a template and hoped worked. Now you can read it line by line and name every piece.

There's the **host**: the object that reads configuration, builds the dependency-injection container, sets up logging, and starts everything. There's the **middleware pipeline**: an ordered chain of `RequestDelegate`s, each one a function that can do work, call the next, and act again on the way back out. There's **endpoint routing**, which is where your minimal API handler or your MVC controller action actually plugs in. And underneath it all there's **Kestrel**, the web server that opens the socket and speaks HTTP. Four pieces. That's the machine.

> 📝 The throughline you should be able to recite without looking: **Kestrel listens, a pipeline of `RequestDelegate`s processes each request, and the host wires up DI, configuration, and the server.** Every `app.Use(...)` and every `builder.Services...` line now has an obvious home.

## What this actually unlocks

This isn't trivia. The pieces you learned are exactly the pieces that bite people in production, and now they'll make sense the moment you hit them.

- **Middleware ordering bugs.** When your authentication runs *after* something that needs the user, or your exception handler sits too low to catch the error, you won't be guessing - you know the pipeline is ordered and each `Use` wraps the next.
- **Auth placement.** `UseAuthentication` then `UseAuthorization`, before your endpoints, after routing. That ordering stops being a memorized spell and becomes "of course - you have to know *who* before you can decide *what they're allowed to do*, and both have to happen before the endpoint runs."
- **Service lifetimes.** "Why is this scoped service blowing up inside a singleton?" makes sense now, because you know what the container is and what a scope means per request.
- **Custom middleware.** You can write your own - a function that wraps the next one - instead of treating middleware as a closed box only the framework gets to open.

The framework stopped being a pile of conventions. It's a small, legible machine, and you can see all of it.

## Performance, now that you can see the cost

Here's a useful side effect of understanding the pipeline: you understand where time goes.

**Kestrel is fast** - among the fastest managed web servers anywhere, and it is not the bottleneck in almost any real app. Your database call is. Your external API is. So the performance advice that actually matters is about respecting the machine you now understand:

- **Keep the pipeline lean.** Every middleware in the chain runs on *every single request*. A middleware you added "just in case" is a tax paid millions of times. If it doesn't earn its place, take it out.
- **Go `async` end to end.** Kestrel's whole model is non-blocking I/O. A synchronous `.Result` or `.Wait()` on a hot path ties up a thread and quietly strangles throughput. Let `await` flow all the way down.
- **Watch allocations on hot paths.** Garbage you create per request is garbage the GC collects per request. On a busy endpoint, that adds up - this is where the profiler, not the guess, earns its keep.

💡 The plain version: don't micro-optimize until you've measured. But *because* you now know the pipeline runs per request and Kestrel is async underneath, you'll write code that's fast by default instead of fast by accident.

## The ecosystem worth knowing

The same host-and-pipeline machine has a few more rooms you'll want to walk into. You don't need them today - but when you do, you'll recognize them as the same building.

- **`IHostedService` / `BackgroundService`.** Work that isn't a request - a queue consumer, a scheduled job, a cache warmer. The host starts and stops these alongside Kestrel. Subclass `BackgroundService`, override `ExecuteAsync`, and you have a clean long-running task with graceful shutdown handled for you.
- **Health checks** (`AddHealthChecks` / `MapHealthChecks`). A built-in endpoint your load balancer or orchestrator can poll to ask "is this app alive and ready?" It's just another endpoint on the routing you already understand.
- **OpenTelemetry.** Tracing and metrics, wired into the host. Once you can trace a request through the pipeline in your head, distributed tracing across services is the same idea, exported.
- **YARP** - Microsoft's reverse proxy, built on *this exact stack*. It's an ASP.NET Core app whose job is forwarding requests. Knowing the pipeline means you can read and configure it.
- **Output caching and rate limiting.** Both ship as built-in middleware. They're `Use`-style entries in the pipeline you now know how to order.

Here's a small `BackgroundService` to make it concrete:

```csharp
public class Heartbeat : BackgroundService
{
    private readonly ILogger<Heartbeat> _log;

    public Heartbeat(ILogger<Heartbeat> log) => _log = log;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _log.LogInformation("still alive at {Time}", DateTimeOffset.Now);
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }
}

// in Program.cs
builder.Services.AddHostedService<Heartbeat>();
```

Notice what you can already read here: it takes a logger from the **DI container**, the **host** starts it, and `stoppingToken` is graceful shutdown handing you the signal to stop. Same machine, different room.

## The same shape, in every language

The most valuable thing you can carry out of this guide is the pattern, not the API surface. What you learned isn't really "ASP.NET Core" - it's how *web stacks* are built. Every serious one is the same three things: a **server** that listens, a **pipeline** that processes, and a **host** that wires it together.

```mermaid
flowchart TB
  subgraph NET[".NET / ASP.NET Core"]
    A1[Kestrel] --> A2[middleware pipeline] --> A3[host + DI]
  end
  subgraph GO["Go / net/http"]
    B1[http.Server] --> B2[Handler wrappers] --> B3[main + wiring]
  end
  subgraph RUST["Rust / hyper + tower"]
    C1[hyper] --> C2[tower Layers] --> C3[main + builder]
  end
```

Read across the rows and it's the same skeleton:

- **The server.** Kestrel ↔ Go's [`http.Server`](/guides/web-services-with-only-net-http) ↔ Rust's [hyper](/guides/hyper-and-tower). Something opens the socket and speaks HTTP.
- **The pipeline.** ASP.NET's `RequestDelegate` chain ↔ Go's `func(http.Handler) http.Handler` wrappers ↔ tower's `Layer`s. A function that wraps the next one, runs before and after, and can short-circuit. Identical idea, three syntaxes.
- **The host / wiring.** The .NET host with its DI container ↔ Go's `main` that constructs and injects by hand ↔ a Rust app's builder. Something assembles the dependencies and starts the server.

You learned one of these deeply. That means you can now open the [Go net/http roots guide](/guides/web-services-with-only-net-http) or the [hyper & tower guide](/guides/hyper-and-tower) and read them as *the same story in a different accent*. The "magic" was always a server, a pipeline, and a host.

## What to do this week

Don't let this stay theory. Three small moves will turn "I read about it" into "I can do it":

1. **Trace one real request.** Open your own app's `Program.cs` and follow a single request all the way through: host builds it → request hits Kestrel → flows through each middleware in order → routing matches → your endpoint runs → the response flows back out through the pipeline. Do it once on paper. It cements everything.
2. **Write a custom middleware.** Something tiny - log the path and how long the request took. You'll feel the "before, call next, after" shape in your hands instead of in the abstract.
3. **Add a `BackgroundService`.** Copy the heartbeat above, watch the host start it on launch and stop it cleanly on shutdown. That's the whole lifecycle, made visible.

If you want to deepen the surface you build on top, go back to [ASP.NET Core From Zero](/guides/aspnet-core-from-zero) - every convenience in it will now read differently, because you can see the plumbing underneath.

**Kestrel listens, a pipeline of `RequestDelegate`s processes each request, and the host wires it all together** - that's every .NET web app, and now it's visible to you.

## Recap

1. **You can read any `Program.cs` now.** Host (config + DI + logging), the middleware pipeline of `RequestDelegate`s, endpoint routing, and Kestrel underneath - four pieces, one legible machine.
2. **The hard bugs make sense.** Middleware ordering, auth placement, service lifetimes, and writing your own middleware all follow directly from knowing the pipeline is an ordered chain and the host owns the container.
3. **Performance is about respecting the machine.** Kestrel is among the fastest managed servers; keep the pipeline lean (it runs per request), go `async` end to end, and watch allocations on hot paths - and measure before optimizing.
4. **The ecosystem is the same building.** `BackgroundService` for non-request work, health checks and output caching and rate limiting as built-in pieces, OpenTelemetry for tracing, and YARP as a proxy built on this exact stack.
5. **Every web stack is server + pipeline + host.** This is the .NET version of [Go's net/http](/guides/web-services-with-only-net-http) and [Rust's hyper/tower](/guides/hyper-and-tower) - learn the shape once and you can read all of them.

## Quick check

Last three - the throughline that should stick:

```quiz
[
  {
    "q": "Why does keeping the middleware pipeline lean matter for performance?",
    "choices": [
      "Every middleware in the chain runs on every single request, so an unused one is a cost paid millions of times",
      "Middleware only runs at startup, so fewer of them means a faster boot",
      "The host removes unused middleware automatically, so it never matters",
      "Middleware runs on a background thread and never affects request latency"
    ],
    "answer": 0,
    "explain": "The pipeline is an ordered chain executed per request. Each middleware you add is work done on every request, so anything that doesn't earn its place is a tax paid at scale."
  },
  {
    "q": "What is a BackgroundService in the host-and-pipeline picture?",
    "choices": [
      "Work that isn't tied to a request - the host starts and stops it alongside Kestrel, with graceful shutdown handled for you",
      "A faster replacement for Kestrel that handles requests in the background",
      "A piece of middleware that runs after the endpoint",
      "A second DI container used only for HTTP requests"
    ],
    "answer": 0,
    "explain": "BackgroundService is for long-running, non-request work (queue consumers, scheduled jobs). The host manages its lifecycle next to the web server, and the stopping token gives you clean shutdown."
  },
  {
    "q": "What is the cross-language parallel you should carry out of this guide?",
    "choices": [
      "Every web stack is a server + a pipeline + a host: Kestrel/pipeline/host maps to Go's http.Server/handler-wrappers/main and Rust's hyper/tower-layers/builder",
      "ASP.NET Core is unique and shares no structure with Go or Rust web stacks",
      "Only Kestrel uses a middleware pipeline; net/http and hyper have nothing comparable",
      "The host is a .NET-only concept with no equivalent in other ecosystems"
    ],
    "answer": 0,
    "explain": "The shape is universal. A server listens, a pipeline of wrap-the-next functions processes each request, and a host wires up dependencies. Learn it once and you can read net/http and hyper/tower as the same story in a different accent."
  }
]
```
