# ASP.NET Core From Zero

> Learn Microsoft's modern, cross-platform web framework: minimal APIs and your first server, routing, model binding and validation, dependency injection, the middleware pipeline, building a REST API, authentication, and testing and production. The framework behind a huge share of enterprise backends, taught mental-model-first.


---

# ASP.NET Core From Zero

ASP.NET Core is Microsoft's modern web framework - cross-platform, fast, and open source - and it sits
under an enormous share of enterprise backends. If you write C# on the server, this is almost certainly
the framework you'll use. The modern version is a clean break from the old .NET Framework days: it runs on
Linux and macOS as happily as Windows, it's genuinely quick, and since .NET 6 it offers **minimal APIs**
that let you stand up an endpoint in a few lines - no ceremony required.

The mental model has two pillars that hold up everything else. First, a request flows through a
**middleware pipeline** - an ordered chain where each piece can act on the request, pass it along, and act
on the response coming back. Second, your code gets its dependencies through **dependency injection**: you
register services in one place, and the framework hands them to whatever needs them. Learn "requests flow
through a pipeline, and services are injected," and the rest of ASP.NET Core - routing, binding, auth - is
detail that hangs off those two ideas.

> 📝 This teaches the **framework** - it assumes you know **C#**: classes, interfaces, generics,
> `async`/`await`, and records ([C# From Zero](/guides/csharp-from-zero)). It pairs with
> [What a Framework Even Is](/guides/what-a-framework-even-is); its data layer is
> [EF Core](/guides/efcore-from-zero); and the server + pipeline beneath it are the roots guide
> [The ASP.NET Pipeline & Kestrel](/guides/the-aspnet-pipeline-and-kestrel). It compiles and runs as a
> .NET program, so examples are shown with the commands to run them.

## How to read this

Read in order - it grows one service (a small **products API**) from a single endpoint to a tested,
deployable REST API. Phases carry difficulty badges.

## The phases

**Part 1 - The core (🟢 Basic → 🟡)**
1. **[What ASP.NET Core Is & Your First Server](01-what-aspnet-core-is.md)** 🟢 - the framework, minimal APIs, and a running app in a few lines.
2. **[Routing & Minimal APIs](02-routing-and-minimal-apis.md)** 🟢 - `MapGet`/`MapPost`, route and query params, and route groups.
3. **[Model Binding & Validation](03-model-binding-and-validation.md)** 🟡 - binding the body/route/query to types, and validating with data annotations.

**Part 2 - A real app (🟡 → 🔴)**
4. **[Dependency Injection](04-dependency-injection.md)** 🟡 - registering services, lifetimes, and constructor injection.
5. **[The Middleware Pipeline](05-middleware-pipeline.md)** 🔴 - `Use`/`Run`/`Map`, ordering, and writing your own middleware.
6. **[Building a REST API](06-building-a-rest-api.md)** 🟡 - full CRUD with `Results`, DI, and a service.
7. **[Authentication & Authorization](07-auth.md)** 🔴 - JWT bearer auth, `[Authorize]`, and protecting endpoints.

**Part 3 - Ship it (🟡 → 🟢)**
8. **[Testing & Production](08-testing-and-production.md)** 🟡 - `WebApplicationFactory` integration tests, config, and deployment.
9. **[Where to Go Next](09-where-to-go-next.md)** 🟢 - minimal APIs vs controllers, EF Core, Blazor, and what to build.

> The throughline: a request travels a **middleware pipeline** to an endpoint, and your code receives its
> collaborators through **dependency injection**. Hold those two and ASP.NET Core is approachable.


---

# What ASP.NET Core Is & Your First Server

You know [C#](/guides/csharp-from-zero) - classes, records, `async`/`await` - and now you want to put
something on the web with it. ASP.NET Core is Microsoft's modern web framework: cross-platform, fast,
open source, and under an enormous share of the world's enterprise backends. If you write C# on the
server, this is the framework you'll meet.

The old .NET Framework was Windows-only and ceremony-heavy. This version runs anywhere, and since .NET 6
it offers **minimal APIs** that let you stand up a working server in a handful of lines - no sprawling
boilerplate, no XML config. A request comes in, your function runs, a response goes out.

> 📝 This guide teaches the **framework**, and it assumes you already know **C#**. It pairs with
> [What a Framework Even Is](/guides/what-a-framework-even-is); its data layer is
> [EF Core](/guides/efcore-from-zero); and the server and pipeline underneath it are the roots guide
> [The ASP.NET Pipeline & Kestrel](/guides/the-aspnet-pipeline-and-kestrel). The examples here are
> regular C# you compile and run - not editable in the browser - so each one comes with the commands
> to run it yourself.

## The mental model: a pipeline and an injector

Two ideas underlie everything else in ASP.NET Core - routing, validation, auth. Learn them now and the
rest reads as detail rather than magic.

📝 **First, a request flows through a middleware pipeline.** It doesn't jump straight to your code - 
it travels through an ordered chain of components, each able to look at the request, do something, hand
it to the next link, and then act on the response coming back. Logging, authentication, error handling:
each is a link in that chain. (Phase 5 builds the pipeline properly.)

📝 **Second, your code receives its dependencies through dependency injection.** Instead of your code
reaching out to create the things it needs - a database connection, a logger, a service - you *register*
those in one place and the framework *hands* them to whatever asks. (Phase 4 covers DI in full.)

💡 Both pillars rest on **Kestrel** - the high-performance web server ASP.NET Core runs on - covered in
[The ASP.NET Pipeline & Kestrel](/guides/the-aspnet-pipeline-and-kestrel). Now let's get a server running.

## Your first server

Create a new project. From a terminal:

```bash
dotnet new web -o MyApi
cd MyApi
```

*What just happened:* `dotnet new web` scaffolded a minimal ASP.NET Core project into a folder called
`MyApi` (`-o` names the output folder). The template is deliberately tiny - no controllers, no extra
files, just a single `Program.cs` and a project file, which is your entire app's starting point. Open it
and you'll find something close to this:

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

app.MapGet("/", () => "Hello from ASP.NET Core");

app.Run();
```

*What just happened:* four lines, and that's a complete web server. Top to bottom:
- `WebApplication.CreateBuilder(args)` creates a **builder** - wires up configuration, logging, and the
  **dependency injection container** you'll later register services in. `args` are command-line
  arguments, passed through so flags can override config.
- `builder.Build()` takes everything the builder set up and produces the finished `app` - a
  `WebApplication`. The builder *configures*; `Build()` *seals it* into a runnable app.
- `app.MapGet("/", () => "...")` registers an **endpoint**: when a `GET` request arrives for path `/`,
  run this lambda.
- `app.Run()` starts **Kestrel** (the server) and begins listening. This call *blocks* - the program
  parks here handling requests until you stop it. Set up anything you need *before* calling `Run()`.

Now run it:

```bash
dotnet run
```

```console
$ dotnet run
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
```

*What just happened:* `dotnet run` compiled and launched your project. `app.Run()` brought Kestrel up,
and it's now listening (the exact port may differ on your machine - read it from the log). Leave it
running, and in another terminal hit the endpoint:

```console
$ curl http://localhost:5000/
Hello from ASP.NET Core
```

*What just happened:* `curl` sent a `GET /`. The request entered the pipeline, reached your `MapGet`
endpoint, the lambda ran, and its return value came back as the response body. You have a working web
server in four lines of real code. Press `Ctrl+C` to stop it.

## Returning text vs. returning an object

The handler above returned a plain `string`, and you got plain text back. ASP.NET Core looks at what
your handler returns and does the sensible thing: a string gets you text, an *object* gets serialized to
**JSON** automatically.

This guide builds toward a small **products API**. First, a type to represent a product - a C# `record`,
perfect for this kind of immutable data:

```csharp
record Product(int Id, string Name, decimal Price);
```

*What just happened:* that one line declares a `Product` with three properties. A positional `record`
gives you the constructor, the properties, and value-based equality for free - exactly what you want for
a data shape that flows in and out as JSON. This `Product` is what we'll spend the next eight phases
turning into a full REST API.

Add a second endpoint that returns one:

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

app.MapGet("/", () => "Hello from ASP.NET Core");

app.MapGet("/products/sample", () => new Product(1, "Keyboard", 49.99m));

app.Run();

record Product(int Id, string Name, decimal Price);
```

*What just happened:* the new `MapGet` returns a `Product` object instead of a string. Because it's an
object, ASP.NET Core serializes it to JSON and sets the `Content-Type` header to `application/json` for
you - no manual serialization, no header fiddling. (The `record` sits at the bottom because in a
top-level `Program.cs`, type declarations come after executable statements.) Run it and ask for the
sample:

```console
$ curl http://localhost:5000/products/sample
{"id":1,"name":"Keyboard","price":49.99}
```

*What just happened:* the object came back as clean JSON, with property names lower-cased the way JSON
conventionally wants them. Two endpoints, two return types, zero extra plumbing.

💡 When you need to control the *status code* too - a `200 OK` with a body, a `404 Not Found`, a
`201 Created` - reach for the `Results` helpers: `Results.Ok(product)`, `Results.NotFound()`, and
friends. We lean on those heavily once we build real CRUD (Phases 2 and 6). For now, returning a value
directly is the quickest way to see data flow.

Next: routing - turning one `/` endpoint into a real set of paths with route and query parameters.

## Recap

- **ASP.NET Core is Microsoft's modern web framework** - cross-platform, fast, open source, and under a
  huge share of enterprise backends.
- **Two pillars:** a request **flows through a middleware pipeline** (Phase 5), and your code
  **receives services via dependency injection** (Phase 4). Both rest on Kestrel, the server.
- **A whole app starts in `Program.cs`.** `CreateBuilder(args)` sets up config, logging, and DI;
  `builder.Build()` produces the `app`; `app.MapGet(...)` registers an endpoint; `app.Run()` starts
  Kestrel and blocks.
- **CLI:** `dotnet new web -o MyApi` scaffolds it, `dotnet run` compiles and launches it, `curl` hits it.
- **Return type decides the response:** a `string` sends text; an object auto-serializes to JSON. Use
  `Results.Ok(...)` / `Results.NotFound()` for explicit status.
- **Running example:** a products API, built on `record Product(int Id, string Name, decimal Price)`.

## Quick check

Three questions on the ideas that have to stick - what ASP.NET Core is, the two pillars, and how a first
server fits together:

```quiz
[
  {
    "q": "In a minimal API Program.cs, what does `WebApplication.CreateBuilder(args)` set up?",
    "choices": [
      "Configuration, logging, and the dependency injection container",
      "Only the route table for your endpoints",
      "The database schema and connection pool",
      "An HTML template engine for rendering pages"
    ],
    "answer": 0,
    "explain": "CreateBuilder produces the builder, which wires up configuration, logging, and the DI container. builder.Build() then turns that into the runnable app, and app.Run() starts Kestrel."
  },
  {
    "q": "What are the two pillars of ASP.NET Core's mental model?",
    "choices": [
      "Requests flow through a middleware pipeline, and your code receives dependencies via dependency injection",
      "A model layer and a view layer, like classic MVC only",
      "Synchronous controllers and asynchronous background jobs",
      "A compiler step and an interpreter step"
    ],
    "answer": 0,
    "explain": "Hold those two ideas and the rest is detail: a request travels an ordered middleware pipeline (Phase 5) to your endpoint, and the framework injects the services your code asks for (Phase 4)."
  },
  {
    "q": "An endpoint handler returns `new Product(1, \"Keyboard\", 49.99m)`. What does the client receive?",
    "choices": [
      "JSON, because ASP.NET Core auto-serializes returned objects and sets the Content-Type",
      "Plain text containing the object's type name",
      "An error, because handlers must return a string",
      "An empty 204 No Content response"
    ],
    "answer": 0,
    "explain": "Returning a string sends text; returning an object auto-serializes to JSON with Content-Type application/json. To control the status code explicitly, use the Results helpers like Results.Ok(...)."
  }
]
```


---

# Routing & Minimal APIs

Here's the whole mental model: **a route is an HTTP method plus a path, pointing at a handler.** `GET /products` is one route; `POST /products` is a different one - same path, different method, different code runs. ASP.NET Core keeps a table of these, and when a request arrives it looks up the method-and-path pair and runs the matching handler.

Minimal APIs are the leanest way to fill that table. `app.MapGet`, `app.MapPost`, and friends each register "when *this* method hits *this* path, run *this* function," usually a small lambda. The clever part: the framework looks at your handler's parameters and **fills them in for you** from the route, the query string, or the request body.

> 📝 You'll keep growing the **products API** from Phase 1. By the end of this phase it'll answer to a list endpoint and a by-id endpoint, return proper status codes, and live under a versioned URL prefix.

## The method maps

Every HTTP method you care about has a matching `Map` call on `app`:

```csharp
app.MapGet("/products", () => "all products");
app.MapPost("/products", () => "create a product");
app.MapPut("/products/{id}", (int id) => $"replace product {id}");
app.MapPatch("/products/{id}", (int id) => $"patch product {id}");
app.MapDelete("/products/{id}", (int id) => $"delete product {id}");
```

*What just happened:* five routes, registered in five lines. `/products` appears twice - `MapGet` and `MapPost` are two separate routes because the method is part of the identity. The string is the path; the lambda is the handler.

Let's make the products list real instead of returning a string, by seeding a tiny in-memory list:

```csharp
var products = new List<Product>
{
    new(1, "Keyboard", 79.99m),
    new(2, "Mouse", 29.99m),
    new(3, "Monitor", 249.00m),
};

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

app.Run();

record Product(int Id, string Name, decimal Price);
```

*What just happened:* The handler returns a `List<Product>`. When you return an object (or a list of them) from a minimal API handler, ASP.NET Core **serializes it to JSON automatically** and sends it back with `Content-Type: application/json`. Return a `string` and you get plain text instead - no serialization code required.

Run it and hit the endpoint:

```bash
curl http://localhost:5000/products
```

You'll get back a JSON array of the three products. So far, so good - but a real API needs to fetch *one* product, and that's where parameters come in.

## Route parameters

A path can contain a **placeholder** in curly braces. Write `{id}` in the route and an `int id` in the handler's parameter list, and ASP.NET Core matches them by name and converts the URL text into the type you asked for:

```csharp
app.MapGet("/products/{id}", (int id) =>
{
    var product = products.FirstOrDefault(p => p.Id == id);
    return product;
});
```

*What just happened:* a request to `/products/2` makes ASP.NET Core pull `"2"` out of the URL, see your parameter is an `int`, parse it, and pass `2` into your lambda as `id`. The match is **by name** - `{id}` binds to the parameter named `id`, not by position.

That automatic conversion has a useful side effect: `/products/banana` can't become an `int`, so the route doesn't match and the framework returns a 400 - your handler never runs with bad data. Make that intent explicit with a **route constraint**, written as `{name:type}`:

```csharp
app.MapGet("/products/{id:int}", (int id) => products.FirstOrDefault(p => p.Id == id));
app.MapGet("/products/category/{slug:alpha}", (string slug) => $"category: {slug}");
```

*What just happened:* `{id:int}` tells the router "only match this route if the segment is an integer." `{slug:alpha}` matches only letters. Constraints filter *whether the route matches at all* - handy when two routes would otherwise collide, like a numeric id versus a text slug in the same position.

> ⚠️ Don't lean on route constraints for *validation*. They decide routing, not correctness - `{id:int}` happily accepts `-999` or `0`. Constraints answer "does this URL belong to this endpoint?" Real input checking is its own job - the whole of Phase 3.

## Query parameters

Route parameters live *in the path*. **Query parameters** live after the `?` - `/products?page=2&q=mouse` - the natural home for optional things like paging, filtering, and search. The binding rule is consistent: any handler parameter that **isn't** named in the route gets pulled from the query string (for simple types like `int`, `string`, `bool`, and their nullable versions).

```csharp
app.MapGet("/products", (int? page, string? q) =>
{
    var results = products.AsEnumerable();

    if (!string.IsNullOrEmpty(q))
        results = results.Where(p => p.Name.Contains(q, StringComparison.OrdinalIgnoreCase));

    var pageNumber = page ?? 1;
    return results.Skip((pageNumber - 1) * 10).Take(10);
});
```

*What just happened:* neither `page` nor `q` appears in the `/products` path, so ASP.NET Core reads them from the query string. `/products?q=mouse` filters by name; `/products?page=2` pages; no query gives defaults because both are nullable (`int?`, `string?`) and arrive as `null` when absent. Nullable types mark a parameter optional.

When the URL name differs from your parameter name, reach for `[FromQuery]`:

```csharp
app.MapGet("/search", ([FromQuery(Name = "term")] string? searchTerm) =>
    $"searching for: {searchTerm}");
```

*What just happened:* `[FromQuery(Name = "term")]` maps the URL's `?term=...` onto a parameter you've named `searchTerm`. You don't need it for the common case, since default binding already does the right thing, but it's there when you want the source spelled out.

## Returning the right status with Results

Returning a raw object is fine until you need to say something other than "200 OK." A by-id lookup that finds nothing should return **404 Not Found**, not `200` with an empty body. A create should return **201 Created**. That's the job of **`Results`** (and its typed sibling `TypedResults`):

```csharp
app.MapGet("/products/{id:int}", (int id) =>
{
    var product = products.FirstOrDefault(p => p.Id == id);
    return product is null
        ? Results.NotFound()
        : Results.Ok(product);
});

app.MapPost("/products", (Product product) =>
{
    products.Add(product);
    return Results.Created($"/products/{product.Id}", product);
});
```

*What just happened:* `Results.Ok(product)` sends the product with a `200`; `Results.NotFound()` sends a `404` with no body. `Results.Created(uri, body)` returns `201` *and* sets the `Location` header to where the new resource lives. `Results` has one method per common outcome: `Ok`, `NotFound`, `Created`, `BadRequest`, more.

`TypedResults` is the same idea with the concrete return type baked in:

```csharp
app.MapGet("/products/{id:int}", (int id) =>
{
    var product = products.FirstOrDefault(p => p.Id == id);
    return product is null
        ? TypedResults.NotFound()
        : TypedResults.Ok(product);
});
```

*What just happened:* behavior is identical at runtime - same status codes, same bodies. The difference is the *type*: `TypedResults.Ok(product)` returns a strongly-typed `Ok<Product>` rather than a general result, easier to unit test and better for tooling. Prefer `TypedResults` for handlers you'll test; `Results` is fine for quick work.

> 💡 A handler can return different result types down different branches. The compiler accepts the `?:` above because `Results.NotFound()` and `Results.Ok(...)` share a common interface, so both branches type-check. With `TypedResults` you'll sometimes declare the return as `Results<Ok<Product>, NotFound>` to keep both concrete types - more on that in later phases.

## Grouping endpoints with MapGroup

As the API grows, every route starts with the same prefix - `/api/v1/products`, `/api/v1/orders`. Repeating `/api/v1` in every `Map` call is noise, and typos creep in. **`MapGroup`** factors out a shared prefix once:

```csharp
var v1 = app.MapGroup("/api/v1");

v1.MapGet("/products", () => products);
v1.MapGet("/products/{id:int}", (int id) =>
{
    var product = products.FirstOrDefault(p => p.Id == id);
    return product is null ? Results.NotFound() : Results.Ok(product);
});
v1.MapPost("/products", (Product product) =>
{
    products.Add(product);
    return Results.Created($"/api/v1/products/{product.Id}", product);
});
```

*What just happened:* `MapGroup("/api/v1")` returns a group object, and every route mapped *on the group* inherits the prefix - no repetition. The immediate payoff is versioning: when `/api/v2` arrives, spin up a second group beside the first, and the two live side by side.

Groups become more powerful later: the same object can attach **authentication, validation filters, and shared metadata** to everything inside it at once. For now, treat `MapGroup` as your tidy prefix - the rest unlocks in the auth and middleware phases.

> 📝 Everything here used **minimal APIs**. ASP.NET Core also has an older, more structured style - **controllers**, classes marked `[ApiController]` with methods decorated by attribute routes like `[HttpGet("products/{id}")]`. Controllers shine in large apps with lots of shared conventions; minimal APIs win on leanness and are the modern default. Not rivals so much as two points on a spectrum - Phase 9 lays them side by side.

## Recap

- A route is an **HTTP method plus a path** pointing at a handler; `MapGet`/`MapPost`/`MapPut`/`MapPatch`/`MapDelete` register them, and the same path with two methods is two distinct routes.
- Handler parameters **bind automatically**: a name that appears in the route (`{id}` → `int id`) comes from the path; one that doesn't comes from the query string. Nullable types (`int?`, `string?`) mark a parameter optional.
- **Route constraints** like `{id:int}` and `{slug:alpha}` decide *whether a route matches* - they are routing filters, not input validation.
- Return a value for the easy case (object → JSON, string → text), or use **`Results`**/**`TypedResults`** to set status precisely: `Ok`, `NotFound`, `Created`, `BadRequest`. `TypedResults` is the testable, strongly-typed variant.
- **`MapGroup`** factors out a shared prefix once (great for `/api/v1` versioning) and later carries shared auth, filters, and metadata for every endpoint inside it.
- Minimal APIs are the modern, lean default; **controllers** are the older structured style - same framework, different ergonomics (full comparison in Phase 9).

## Quick check

```quiz
[
  {
    "q": "In app.MapGet(\"/products/{id:int}\", (int id) => ...), where does the value of id come from, and what does :int do?",
    "choices": ["From the request body; :int validates that id is positive", "From the route path; :int constrains the route to match only when the segment is an integer", "From the query string; :int converts the value to an integer", "From an HTTP header named id; :int is ignored at runtime"],
    "answer": 1,
    "explain": "{id} is a route placeholder, so id binds from the path by name. The :int constraint controls whether the route matches at all - it filters routing, it is not input validation."
  },
  {
    "q": "A handler is written as (int? page, string? q) => ... and neither name appears in the route path. Where do page and q bind from?",
    "choices": ["From the request body as JSON properties", "From the route path", "From the query string (e.g. ?page=2&q=mouse)", "They must be supplied with [FromServices] dependency injection"],
    "answer": 2,
    "explain": "A simple-typed handler parameter that is not named in the route binds from the query string. The nullable types make them optional, so they arrive as null when absent."
  },
  {
    "q": "Your by-id endpoint finds no matching product. Which return best signals that to the client?",
    "choices": ["return product; (returns 200 with an empty body)", "Results.NotFound() to return a 404", "Results.Created(...) to return a 201", "Throw an exception so the pipeline returns 500"],
    "answer": 1,
    "explain": "A missing resource is a 404. Results.NotFound() sends that status with no body; returning a null object would send a 200, which misleads the client. (TypedResults.NotFound() does the same, with a testable type.)"
  }
]
```


---

# Model Binding & Validation

Here's the mental model: **a raw HTTP request is just bytes - a URL, some headers, maybe a blob of JSON. Model binding turns those bytes into typed C# parameters. Validation then checks those values are actually sane before your real logic runs.** Two steps, in order: shape the data, then trust the data.

Phase 2 had handlers take parameters and ASP.NET Core somehow filled them in - this phase is the "somehow." We'll keep growing the **products API** and look plainly at a sharp edge that trips up nearly everyone moving from controllers to minimal APIs.

## Where does each parameter come from?

For each parameter in a minimal API handler, ASP.NET Core decides *which part of the request* fills it using a small set of inference rules:

- A parameter whose name matches a **route placeholder** binds from the route.
- A **simple type** (string, int, `Guid`, etc.) that isn't in the route binds from the **query string**.
- A **complex type** (your own class or record) binds from the **JSON body**.
- Known framework types (like a `CancellationToken` or a registered service) are supplied by the framework.

Most of the time you don't annotate anything - inference just works.

```csharp
// GET /products/42?fields=name
app.MapGet("/products/{id:int}", (int id, string? fields) =>
{
    // id  ← from the route ("{id:int}")
    // fields ← from the query string ("?fields=name")
    return Results.Ok(new { id, fields });
});
```

*What just happened:* `id` matched the route placeholder, so it bound from the path. `fields` is a simple type with no matching route segment, so ASP.NET Core looked in the query string. Nothing was annotated - names and types told the framework everything.

### When you need to be explicit

Inference is a default, not a law. To override it, or just make the source obvious to the next reader, reach for the `[From*]` attributes:

| Attribute | Binds from |
|-----------|------------|
| `[FromBody]` | the request body (JSON) |
| `[FromRoute]` | a route placeholder |
| `[FromQuery]` | the query string |
| `[FromHeader]` | a request header |
| `[FromServices]` | the dependency-injection container (more on this in [Dependency Injection](04-dependency-injection.md)) |

> 💡 You rarely *need* `[FromBody]` for a complex type - it's already inferred. But you can only have **one** body-bound parameter per handler (a request has one body), and that's a common source of "why is this null?" confusion when you accidentally mark two parameters to read the body.

## Binding the body to a record

The bread-and-butter case: a POST that creates a product. The client sends JSON, landed in a `CreateProduct` record.

```csharp
app.MapPost("/products", (CreateProduct input) =>
{
    var id = Guid.NewGuid();
    var product = new Product(id, input.Name, input.Price);
    // (save it somewhere - that's Phase 6's job)
    return Results.Created($"/products/{id}", product);
});

public record CreateProduct(string Name, decimal Price);
```

*What just happened:* `CreateProduct` is a complex type, so ASP.NET Core deserialized the JSON body into it - matching `Name` and `Price` by property name (case-insensitive by default). POST `{ "name": "Keyboard", "price": 49.99 }` and `input` arrives fully populated. You return `201 Created` with a `Location` header.

📝 If the JSON is *malformed*, binding fails before your handler runs and the client gets a `400`. But if it's well-formed yet *nonsense for your domain* - an empty name, a negative price - binding happily succeeds. The bytes parsed fine; they're just bad data. Catching that is validation's job.

## Validation with data annotations

ASP.NET Core's built-in validation is **DataAnnotations**: attributes on the properties of your bound type that declare the rules. The common ones:

```csharp
using System.ComponentModel.DataAnnotations;

public class CreateProduct
{
    [Required]
    [StringLength(120, MinimumLength = 1)]
    public string Name { get; set; } = "";

    [Range(0, 100000)]
    public decimal Price { get; set; }
}
```

*What just happened:* we declared, right next to the data, what "valid" means - `Name` must be present and at most 120 characters, `Price` must sit between 0 and 100,000. (We switched from a `record` to a `class` with settable properties; a mutable class is the more common shape for a validated input model.) These attributes are pure declarations - they don't *do* anything on their own, and that's the part that surprises people.

## ⚠️ The plain minimal-API gotcha

Here's the thing nobody warns you about until it bites: **minimal APIs do not automatically run DataAnnotations validation.** Decorate every property with `[Required]` and `[Range]` you like - a minimal API handler will run anyway, with an empty name and a price of -5, because nothing in the default pipeline ever checked.

This catches experienced ASP.NET developers especially hard, because in **MVC controllers** it *does* happen automatically (more below). In a minimal API, the rules are documentation until you wire up an enforcer. Three straightforward options:

1. **Validate manually** in the handler.
2. **Add an endpoint filter** that validates every request to that endpoint.
3. **Use a library** - `MinimalApis.Extensions` / `MiniValidation` (a tiny helper that runs DataAnnotations for you) or **FluentValidation** (rules in separate validator classes, popular on bigger teams).

Let's do option 1 so you can *see* the machinery - then you'll appreciate why the others exist.

```csharp
using System.ComponentModel.DataAnnotations;

app.MapPost("/products", (CreateProduct input) =>
{
    var context = new ValidationContext(input);
    var results = new List<ValidationResult>();

    if (!Validator.TryValidateObject(input, context, results, validateAllProperties: true))
    {
        // turn failures into the shape ValidationProblem wants:
        // { "Name": ["The Name field is required."], ... }
        var errors = results
            .SelectMany(r => r.MemberNames.Select(name => (name, r.ErrorMessage)))
            .GroupBy(x => x.name, x => x.ErrorMessage ?? "Invalid")
            .ToDictionary(g => g.Key, g => g.ToArray());

        return Results.ValidationProblem(errors);
    }

    var product = new Product(Guid.NewGuid(), input.Name, input.Price);
    return Results.Created($"/products/{product.Id}", product);
});
```

*What just happened:* `Validator.TryValidateObject` is the engine that reads the annotations and runs them (`validateAllProperties: true` checks every property instead of stopping at the first). On failure we reshape the results into a dictionary of field → messages and hand it to `Results.ValidationProblem`, which returns a `400` with a standard **ProblemDetails** body. On success we proceed to create the product - validation runs *before* the create logic, that ordering is the whole point.

> 💡 In real projects you'd lift that block into an **endpoint filter** or let **MiniValidation** do the `TryValidateObject` dance for you. The manual version above is here so the magic isn't magic.

## Why some teams still reach for controllers

📝 If validation being automatic sounds appealing, you're not alone - that's one real reason teams pick MVC **controllers** over minimal APIs. A controller marked `[ApiController]` validates the bound model *for you*: it runs the DataAnnotations, populates `ModelState`, and short-circuits with a `400` and a ProblemDetails body **before your action method ever runs**.

```csharp
[ApiController]
[Route("products")]
public class ProductsController : ControllerBase
{
    [HttpPost]
    public IActionResult Create(CreateProduct input)
    {
        // If we got here, input is already valid.
        // [ApiController] auto-returned 400 otherwise - ModelState was checked for us.
        var product = new Product(Guid.NewGuid(), input.Name, input.Price);
        return Created($"/products/{product.Id}", product);
    }
}
```

*What just happened:* `[ApiController]` opted this class into a bundle of conventions, one being automatic model validation. By the time `Create` runs, the framework has already inspected `ModelState`; if `Name` was empty it never called your method. You write less plumbing; you give up some of the explicitness of minimal APIs. Neither choice is wrong - it's a trade.

Pick whichever fits the project. This guide stays on minimal APIs and wires validation in deliberately - it keeps the "request flows in, gets shaped, gets checked" model visible instead of hidden behind a convention.

## Recap

- **Binding turns the request into typed C# parameters; validation checks those values** - always in that order, before your logic runs.
- In minimal APIs the source is **inferred**: route placeholders by name, simple types from the query string, a **complex type from the JSON body**. Override with `[FromBody]`, `[FromRoute]`, `[FromQuery]`, `[FromHeader]`, `[FromServices]`.
- **DataAnnotations** (`[Required]`, `[StringLength]`, `[Range]`, `[EmailAddress]`) declare validity right on the model - but they're inert until something runs them.
- ⚠️ **Minimal APIs do NOT auto-validate.** Validate manually, add an endpoint filter, or use MiniValidation / FluentValidation. On failure return `Results.ValidationProblem(errors)` for a standard `400`.
- 💡 **`[ApiController]` controllers DO auto-validate** via `ModelState` and return `400` automatically - a real reason some teams still choose controllers.

## Quick check

```quiz
[
  {
    "q": "In a minimal API, where does a complex type (like CreateProduct) bind from by default?",
    "choices": ["The route values", "The query string", "The JSON request body", "Request headers"],
    "answer": 2,
    "explain": "Inference binds simple types from the query and route, and a complex type from the JSON body. Override with [From*] attributes if needed."
  },
  {
    "q": "You decorated CreateProduct with [Required] and [Range], but your minimal API handler still runs with bad data. Why?",
    "choices": ["The attributes are spelled wrong", "Minimal APIs don't run DataAnnotations validation automatically", "You forgot [FromBody]", "DataAnnotations only work on GET requests"],
    "answer": 1,
    "explain": "Minimal APIs don't auto-validate. You must validate manually, add an endpoint filter, or use a library like MiniValidation or FluentValidation."
  },
  {
    "q": "What does a controller marked with [ApiController] do that a plain minimal API does not?",
    "choices": ["Binds the JSON body", "Automatically validates the model and returns 400 before your method runs", "Generates routes from the method name", "Runs faster"],
    "answer": 1,
    "explain": "[ApiController] auto-runs DataAnnotations, populates ModelState, and short-circuits with a 400 + ProblemDetails when validation fails - before your action executes."
  }
]
```


---

# Dependency Injection

Here's the mental model: **you register services in one place, and the framework constructs them and hands them to whatever asks.** You stop writing `new ProductRepository()` scattered through your code. Instead you say, once, "when something needs an `IProductRepository`, give it a `ProductRepository`," and the framework does the wiring - code against interfaces, not `new`.

> 📝 The container that does this wiring is built into ASP.NET Core - nothing to install, no third-party library to bolt on. The moment you call `WebApplication.CreateBuilder(args)`, you already have a dependency-injection container sitting on `builder.Services`, waiting for you to register things.

## Why bother - the problem DI solves

Imagine the products endpoint reaches straight for the data layer:

```csharp
app.MapGet("/products", () =>
{
    var repo = new ProductRepository();   // hard-wired to one concrete class
    return repo.All();
});
```

*What just happened:* the endpoint creates its own repository with `new`. It works, but is welded to that exact class. To test it, you'd hit the real data store; to swap implementations (in-memory for tests, cached in production), you'd edit every place that calls `new`. The endpoint knows *too much* about how a repository gets built.

Dependency injection flips this: the endpoint declares *what it needs* (an `IProductRepository`) and lets the framework decide *what to hand over* and *how to build it*, so it stops caring about construction entirely.

## Step 1: program to an interface

DI works best when you depend on an interface, not a concrete class. Define the contract first, then an implementation:

```csharp
public interface IProductRepository
{
    IEnumerable<Product> All();
    Product? Find(int id);
}

public class ProductRepository : IProductRepository
{
    private readonly List<Product> _products =
    [
        new(1, "Keyboard", 49.99m),
        new(2, "Mouse", 24.99m),
    ];

    public IEnumerable<Product> All() => _products;
    public Product? Find(int id) => _products.FirstOrDefault(p => p.Id == id);
}

public record Product(int Id, string Name, decimal Price);
```

*What just happened:* `IProductRepository` is the contract - the *what*. `ProductRepository` is one *how*. Endpoints depend only on the interface, so the concrete class becomes a swappable detail. (This is the in-memory list from earlier phases, now hidden behind an interface.)

## Step 2: register the service

Now tell the container about the mapping, on `builder.Services`, before `builder.Build()`:

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

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

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

*What just happened:* You registered a rule: "whenever someone asks for `IProductRepository`, construct a `ProductRepository` and supply it." The two type arguments are the contract and the implementation, in that order. `AddScoped` is *one* of three lifetimes, which control *how often* the framework builds a fresh instance - the next thing to understand.

## Step 3: the three lifetimes

Registering a service answers one question: **how long should a single instance live before the framework throws it away and builds a new one?** Three answers:

| Method | One instance per… | Reach for it when… |
|--------|-------------------|--------------------|
| `AddSingleton` | the whole app | the service is stateless or holds shared, long-lived state (config, an in-memory cache, a clock) |
| `AddScoped` | one HTTP request | the service should be fresh per request - the **default** for data access like a `DbContext` or repository |
| `AddTransient` | every single resolution | the service is lightweight and you want a brand-new one each time it's asked for |

```csharp
builder.Services.AddSingleton<IClock, SystemClock>();           // one forever
builder.Services.AddScoped<IProductRepository, ProductRepository>(); // one per request
builder.Services.AddTransient<IPriceFormatter, PriceFormatter>();    // one each time
```

*What just happened:* three registrations, three lifecycles. `IClock` is built once and shared for the life of the app. The repository is built once *per incoming HTTP request* - two simultaneous requests get two separate repositories, but within one request everyone shares the same one. The formatter is built fresh every time anything asks for it.

> 💡 When in doubt, **`AddScoped` is the sensible default** for application services and anything touching data - it gives each request its own clean instance and lets the framework dispose it when the request ends. Reach for `Singleton` only for genuinely shared state, and `Transient` for cheap, stateless helpers.

## Step 4: consume the service

You registered it - now use it. In a minimal API, you don't fetch the service, you **declare it as a handler parameter** and the framework supplies it:

```csharp
app.MapGet("/products", (IProductRepository repo) => repo.All());

app.MapGet("/products/{id:int}", (int id, IProductRepository repo) =>
    repo.Find(id) is { } product
        ? Results.Ok(product)
        : Results.NotFound());
```

*What just happened:* the handler asks for an `IProductRepository` in its parameter list. The framework recognizes it as a registered service, builds one per its lifetime, and passes it in. You never wrote `new` - swap the registration and every handler quietly gets the new implementation.

In classes - controllers, your own services, background workers - injection happens through the **constructor** instead:

```csharp
public class ProductService
{
    private readonly IProductRepository _repo;

    public ProductService(IProductRepository repo)   // framework supplies this
    {
        _repo = repo;
    }

    public decimal TotalCatalogValue() => _repo.All().Sum(p => p.Price);
}
```

*What just happened:* `ProductService` declares its dependency as a constructor parameter and stashes it in a `readonly` field. When something asks the container for a `ProductService`, the framework sees the constructor needs an `IProductRepository`, builds that too, and passes it in - a chain of construction you never manage by hand.

> 💡 Most minimal-API handlers can tell your services from your bound data by their types. If the framework can't tell whether a parameter is a service or request data, mark it with `[FromServices]`: `app.MapGet("/total", ([FromServices] ProductService svc) => svc.TotalCatalogValue());`.

## The trap: captive dependencies

The pitfall that bites everyone eventually. Lifetimes have a rule: **a service can safely depend on others of the same lifetime or longer-lived ones - but not shorter-lived ones.**

The classic violation is injecting a **Scoped** service into a **Singleton**:

```csharp
// ⚠️ DON'T do this
public class ProductCache
{
    private readonly IProductRepository _repo;   // Scoped...
    public ProductCache(IProductRepository repo) // ...captured by a Singleton
    {
        _repo = repo;
    }
}

builder.Services.AddSingleton<ProductCache>();   // lives for the whole app
```

*What just happened:* `ProductCache` is a singleton - built once, kept forever. But it grabs a `ProductRepository`, which is *scoped* - meant to live for one request and then be disposed. Because the singleton holds onto it, that repository never gets released: it's been **captured**, and every request unknowingly shares the same stale instance. EF Core's `DbContext` - scoped by default - is the textbook casualty; a singleton clutching one surfaces as bizarre data corruption under load.

> ⚠️ Rule of thumb: **never inject something shorter-lived into something longer-lived.** Scoped-into-singleton and transient-into-singleton are the dangerous pairs. The fix is usually to make the outer service scoped too, or - when a singleton genuinely needs per-request work - inject an `IServiceScopeFactory` and create a scope on demand. The built-in container even has a "scope validation" check that throws on these mistakes in development, so it often catches you before production does.

## Why this is the backbone of testable code

Notice what DI bought you. Your endpoints and services depend on `IProductRepository`, never on `ProductRepository`. In a test, you register a fake - an in-memory or stubbed implementation of the same interface - and *every* consumer transparently uses it, no production code changed. The framework also owns the lifecycle: it builds your services, hands them around, and disposes them at the right moment.

That's the payoff: **decoupling** plus **managed lifetimes** - what makes ASP.NET Core code straightforward to test, the muscle you'll flex in [Phase 8: Testing & Production](08-testing-and-production.md).

## Recap

- **The model:** register services in one place; the framework constructs them and supplies them to whatever asks. You code against interfaces, not `new`.
- **Register** on `builder.Services` against an interface, e.g. `AddScoped<IProductRepository, ProductRepository>()` - contract first, implementation second.
- **Three lifetimes:** `AddSingleton` (one per app), `AddScoped` (one per HTTP request - the default for data access like a `DbContext`), `AddTransient` (a new one every resolution).
- **Consume** via handler parameters in minimal APIs and via the **constructor** in classes; use `[FromServices]` to disambiguate when needed.
- **Captive dependency:** never inject a shorter-lived service into a longer-lived one - a Scoped (or Transient) thing captured by a Singleton outlives its scope and breaks under load.
- **Payoff:** decoupling plus framework-managed lifetimes, which is what makes your code testable.

## Quick check

```quiz
[
  {
    "q": "Which lifetime gives you one instance per HTTP request and is the sensible default for a repository or DbContext?",
    "choices": ["AddSingleton", "AddScoped", "AddTransient", "AddRequest"],
    "answer": 1,
    "explain": "AddScoped builds one instance per HTTP request, shared within that request and disposed when it ends - ideal for data access like a DbContext."
  },
  {
    "q": "In a minimal API, how does a handler receive a registered service?",
    "choices": ["By calling new on the concrete class", "By declaring it as a handler parameter, which the framework supplies", "By reading it from a global static field", "By passing it in the route template"],
    "answer": 1,
    "explain": "You declare the service as a handler parameter (e.g. (IProductRepository repo)); the framework recognizes the registered type and injects it."
  },
  {
    "q": "Why is injecting a Scoped service into a Singleton a bug?",
    "choices": ["Singletons can't take constructor parameters", "The Scoped service gets captured and outlives its scope, so every request shares one stale instance", "Scoped services are slower than Singletons", "The container refuses to register any Singleton"],
    "answer": 1,
    "explain": "The singleton holds the scoped instance forever - a captive dependency. It never gets disposed and is shared across all requests, causing hard-to-reproduce bugs under load (classic with EF Core's DbContext)."
  }
]
```


---

# The Middleware Pipeline

Back in the overview I told you ASP.NET Core stands on two pillars. Phase 4 covered dependency injection. This phase is the other pillar, and once it clicks, an enormous amount of the framework stops looking like magic.

**Every incoming request travels through an ordered chain of small pieces of code called middleware.** Each piece gets a chance to look at the request on the way *in*, then hands control to the next piece down the chain, and finally gets a chance to look at the response on the way *back out*. It's an onion: the request goes inward layer by layer to reach your endpoint, and the response unwinds outward through those same layers in reverse.

That "in, then back out" shape is the single most important thing to hold onto: a middleware isn't a one-shot handler, it wraps everything after it.

```mermaid
flowchart LR
  R[Request] --> A[Logging] --> B[HTTPS redirect] --> C[Auth] --> E[Your endpoint]
  E --> C2[Auth] --> B2[HTTPS redirect] --> A2[Logging] --> O[Response]
```

> 💡 The chain isn't a metaphor invented for teaching - under the hood it's really a stack of nested functions, each holding a reference to the next as a `RequestDelegate`. We stay at the using-it level here; the machinery beneath (the `RequestDelegate` type, and Kestrel handing requests in) is the roots guide [The ASP.NET Pipeline & Kestrel](/guides/the-aspnet-pipeline-and-kestrel).

## `Use`, `Run`, and `Map`

You build the pipeline in `Program.cs` by adding middleware to your `WebApplication` (the `app` variable). Three verbs you'll reach for.

**`app.Use`** adds a middleware that *may* call the next one. It receives the request `context` and a `next` delegate: you do your work, `await next(context)` to run the rest of the pipeline, then do more work after it returns. This is the onion in code. Here's a timing-and-logging middleware on the products API - it stamps how long every request took:

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

app.Use(async (context, next) =>
{
    var start = DateTime.UtcNow;
    await next(context);              // run the rest of the pipeline
    var ms = (DateTime.UtcNow - start).TotalMilliseconds;
    app.Logger.LogInformation("{Method} {Path} -> {Status} in {Ms}ms",
        context.Request.Method, context.Request.Path, context.Response.StatusCode, ms);
});

app.MapGet("/products", () => new[] { new { Id = 1, Name = "Keyboard" } });

app.Run();
```

*What just happened:* the lambda runs once per request. Everything before `await next(context)` happens on the way in (we grab a start time). `await next(context)` runs *all* the middleware and the endpoint below us - `/products` actually returns its data during that await. Control then comes back and everything after the await runs on the way out, when we know the status code and elapsed time. One middleware, both edges of the onion.

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

```csharp
app.Run(async context =>
{
    await context.Response.WriteAsync("Nothing here.");
});
```

*What just happened:* `Run` has no way to pass control onward, so it always produces the response itself. You'll use it far less than `Use` - mostly as a catch-all at the bottom of the pipeline. The name is the giveaway: `Use` participates in the chain, `Run` ends it.

**`app.Map`** branches the pipeline based on the request path. Anything matching the prefix gets its own little sub-pipeline:

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

*What just happened:* requests starting with `/admin` peel off into the branch and run only what's configured inside it; everything else flows past untouched. There's also `app.MapWhen(predicate, branch)` to branch on something other than a path - say, the presence of a header or a query string value. `Map` is for "this whole slice of the app behaves differently."

## ⚠️ Order matters - a lot

This is where people get burned, so read it twice. **Middleware runs in the exact order you add it.** The first one registered is the outermost layer of the onion; the last is closest to your endpoint. Move two lines and you can silently break security or error handling.

Why does it bite so hard? Because built-in middleware *depends* on running in a particular order. Authorization can't decide whether to allow a request until authentication has figured out *who* the request is from. Routing has to match an endpoint before authorization can read that endpoint's `[Authorize]` rules. So there's a canonical order, and it's not negotiable:

```csharp
app.UseExceptionHandler("/error");   // outermost - must wrap everything to catch errors
app.UseHttpsRedirection();           // bounce http -> https early
app.UseStaticFiles();                // serve files before hitting routing/auth

app.UseRouting();                    // figure out WHICH endpoint matches
app.UseAuthentication();             // WHO is this request? (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:* the exception handler goes first so it wraps every later layer - only an outer layer can catch what an inner layer throws. `UseRouting` runs before the auth pair because authorization needs to know which endpoint was selected to read its permissions. `UseAuthentication` always precedes `UseAuthorization` - you must establish identity before checking permissions. Endpoints come last, after auth has had its say. The rule of thumb that covers most mistakes: **put auth before the endpoints it protects.** If `UseAuthorization` lands after your `MapGet`, the endpoint runs before anyone checks permissions, and your `[Authorize]` rules do nothing.

> 📝 Minimal API apps wire a lot of this up implicitly - call `app.UseAuthentication()`/`app.UseAuthorization()` and the framework slots `UseRouting` in for you. But the *ordering law* is the same whether it's implicit or spelled out. When something auth-related behaves strangely, the order of these lines is the first place to look.

## Short-circuiting: when *not* calling `next` is the point

A middleware that calls `next` is a pass-through. One that **doesn't** call `next` ends the request right there and sends a response - this is called **short-circuiting**, and it's not a bug, it's a primary tool.

This is exactly how auth, caching, and rate-limiting reject requests early without wasting work on the endpoint. Here's a hand-rolled API-key gate in front of the products API:

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

    await next(context);   // key is good - let the request continue inward
});

app.MapGet("/products", () => Results.Ok(new[] { "Keyboard", "Mouse" }));
```

*What just happened:* when the key is wrong we set a 401, write a message, and `return` - we never call `next`, so the request never reaches `/products`. The endpoint does zero work for a request that was never going to be allowed. When the key is good, `await next(context)` lets it flow onward as normal. That fork - "reject now, or pass it down" - is the entire job of authentication and authorization middleware, just with real tokens instead of a hardcoded string. (You'll see the real, built-in version in [Phase 7: Authentication & Authorization](07-auth.md); don't ship a hardcoded key like this one.)

## Writing a reusable middleware

Inline lambdas are perfect for small things. When a middleware grows, or you want to reuse it, pull it into a class: a constructor that takes the `RequestDelegate next`, plus an `InvokeAsync(HttpContext)` method:

```csharp
public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestTimingMiddleware> _logger;

    public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var start = DateTime.UtcNow;
        await _next(context);                               // pass control down
        var ms = (DateTime.UtcNow - start).TotalMilliseconds;
        _logger.LogInformation("{Path} took {Ms}ms", context.Request.Path, ms);
    }
}

// In Program.cs, where you'd otherwise call app.Use(...):
app.UseMiddleware<RequestTimingMiddleware>();
```

*What just happened:* this is the exact same onion as our first lambda, just in class form. The `RequestDelegate next` the constructor receives *is* "the rest of the pipeline" - calling `_next(context)` is the same as `await next(context)` earlier. Notice `ILogger` arriving through the constructor: middleware classes get their dependencies via the DI from Phase 4. `app.UseMiddleware<T>()` registers it at whatever point in the order you place that line - all the ordering rules above still apply.

## Recap

- A request flows through an **ordered chain of middleware**, each running on the way *in*, calling `next` to go deeper, then running again on the way *out*. Think onion. This is one of ASP.NET Core's two pillars; DI is the other.
- **`app.Use`** adds middleware that may call `next`; **`app.Run`** is terminal (never calls `next`); **`app.Map`/`MapWhen`** branch the pipeline on a path or condition.
- **Order is the law.** Middleware runs in registration order. The canonical built-in order is `UseExceptionHandler` (and `UseHttpsRedirection`) early, then `UseRouting` → `UseAuthentication` → `UseAuthorization` → endpoints. Put auth before the endpoints it protects.
- **Not calling `next` short-circuits** the pipeline - the request gets a response immediately and never reaches the endpoint. That's how auth, caching, and rate-limiting reject early.
- For anything beyond a small lambda, write a middleware **class** (`RequestDelegate next` in the constructor, `InvokeAsync(HttpContext)`) and register it with `app.UseMiddleware<T>()`. It gets DI like any other service.
- The machinery underneath - the `RequestDelegate` and Kestrel - lives in [The ASP.NET Pipeline & Kestrel](/guides/the-aspnet-pipeline-and-kestrel).

Quick gut-check before moving on:

```quiz
[
  {
    "q": "What is the difference between app.Use and app.Run?",
    "choices": [
      "Use runs only in development; Run runs only in production",
      "Use may call next to continue the pipeline; Run is terminal and never calls next",
      "Use is for GET requests; Run is for POST requests",
      "There is no difference - they are aliases"
    ],
    "answer": 1,
    "explain": "app.Use receives a next delegate and can pass control onward; app.Run is the end of the chain and always produces the response itself."
  },
  {
    "q": "Why must UseAuthentication be added before UseAuthorization?",
    "choices": [
      "Alphabetical order is required by the compiler",
      "Authorization decides who the user is, then Authentication checks permissions",
      "You must establish WHO the request is from before you can check WHAT they're allowed to do",
      "It doesn't matter - middleware order is ignored for auth"
    ],
    "answer": 2,
    "explain": "Authentication establishes identity; authorization checks permissions against that identity. Order matters because middleware runs in the order you register it."
  },
  {
    "q": "A middleware sets a 401 status and returns WITHOUT calling next. What happens?",
    "choices": [
      "The request still reaches the endpoint, which overrides the 401",
      "The pipeline short-circuits - the request never reaches the endpoint and the 401 response is sent",
      "ASP.NET Core throws an exception because next is required",
      "The request restarts from the top of the pipeline"
    ],
    "answer": 1,
    "explain": "Not calling next short-circuits the pipeline: the response is sent immediately and inner middleware/endpoints never run. This is exactly how auth rejects requests early."
  }
]
```


---

# Building a REST API

Here's the mental model that turns five separate phases into one coherent thing: **a REST resource is five endpoints over one collection.** List them all, fetch one, create one, replace one, delete one. That's it. Every framework you'll ever touch - Express, Rails, Django, Spring - expresses this same five-fingered shape. In ASP.NET Core you express it with minimal APIs: each endpoint wired through **dependency injection** to a repository, returning a **typed `Results`** that says exactly what HTTP status it means.

This phase isn't new material - it's the payoff. You already have the pieces: routing and route groups ([Phase 2](02-routing-and-minimal-apis.md)), binding the request body into a type and validating it ([Phase 3](03-model-binding-and-validation.md)), and the injected `IProductRepository` ([Phase 4](04-dependency-injection.md)). Time to snap them together into a real, working `/api/v1/products` API.

> 📝 The thing to hold onto: each endpoint is just *bind the input → call the repository → return a `Results` with the right status code*. Once you see that one pattern, all five are variations on it.

## The repository, with a real store behind it

In Phase 4 the `IProductRepository` only knew how to read - `All()` and `Find(id)`. A CRUD API needs to write too, so widen the contract and give it a store that can actually hold new products:

```csharp
public interface IProductRepository
{
    IEnumerable<Product> All();
    Product? Find(int id);
    Product Add(string name, decimal price);
    Product? Update(int id, string name, decimal price);
    bool Delete(int id);
}

public record Product(int Id, string Name, decimal Price);
```

*What just happened:* The interface grew three write methods. `Add` returns the created `Product` (so the caller learns the new `Id`), `Update` returns `Product?` - `null` means "no such id" - and `Delete` returns a bool for found-or-not. The endpoints lean on those return shapes to choose their status codes.

Now the implementation. Earlier phases used a plain `List<Product>`, fine for reads. But a CRUD API mutates shared state, and **incoming requests run concurrently** - ASP.NET Core handles many at once on different threads. Two simultaneous `POST`s racing on a `List` and an `int` counter will corrupt it or hand out duplicate ids, so the store has to be thread-safe:

```csharp
using System.Collections.Concurrent;

public class ProductRepository : IProductRepository
{
    private readonly ConcurrentDictionary<int, Product> _products = new();
    private int _nextId = 0;

    public ProductRepository()
    {
        Add("Keyboard", 49.99m);
        Add("Mouse", 24.99m);
    }

    public IEnumerable<Product> All() => _products.Values;

    public Product? Find(int id) =>
        _products.TryGetValue(id, out var product) ? product : null;

    public Product Add(string name, decimal price)
    {
        var id = Interlocked.Increment(ref _nextId);
        var product = new Product(id, name, price);
        _products[id] = product;
        return product;
    }

    public Product? Update(int id, string name, decimal price)
    {
        if (!_products.ContainsKey(id)) return null;
        var updated = new Product(id, name, price);
        _products[id] = updated;
        return updated;
    }

    public bool Delete(int id) => _products.TryRemove(id, out _);
}
```

*What just happened:* `ConcurrentDictionary<int, Product>` gives thread-safe reads, writes, and removes without hand-rolling locks. `Interlocked.Increment` bumps the id counter atomically, so two racing `Add`s can never collide on the same id. The constructor seeds a couple of products so the API isn't empty on first run. Same idea as before - an in-memory store hidden behind the interface - hardened for the fact that real requests overlap.

> ⚠️ The trap here is subtle because it doesn't show up until *load*. A plain `List` and `count++` work perfectly when you test by hand, one request at a time - then fall over in production when traffic overlaps. If a collection is shared across requests and gets written to, reach for a concurrent collection (or a lock). Don't wait for the heisenbug.

Register it exactly as in Phase 4 - contract first, implementation second. Use `AddSingleton` here so the in-memory store survives across requests (a `Scoped` repository would build a fresh, empty dictionary every request and "forget" everything):

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IProductRepository, ProductRepository>();
var app = builder.Build();
```

*What just happened:* One registration, and every endpoint that declares an `IProductRepository` parameter now gets the same shared instance. The singleton lifetime is deliberate: it's an in-memory store, so it *needs* to outlive a single request to remember added products. (When you move to a real database, that flips back to `Scoped` - more on that at the end.)

## The five endpoints, grouped

Rather than scatter `MapGet`/`MapPost` calls with the same `/api/v1/products` prefix repeated five times, group them. A `MapGroup` declares the shared path once and hangs endpoints off it:

```csharp
var products = app.MapGroup("/api/v1/products");

// GET /api/v1/products  → list all
products.MapGet("/", (IProductRepository repo) =>
    Results.Ok(repo.All()));

// GET /api/v1/products/{id}  → one, or 404
products.MapGet("/{id:int}", (int id, IProductRepository repo) =>
    repo.Find(id) is { } product
        ? Results.Ok(product)
        : Results.NotFound());

// POST /api/v1/products  → create, 201 with Location
products.MapPost("/", (CreateProduct input, IProductRepository repo) =>
{
    var product = repo.Add(input.Name, input.Price);
    return Results.Created($"/api/v1/products/{product.Id}", product);
});

// PUT /api/v1/products/{id}  → replace, or 404
products.MapPut("/{id:int}", (int id, CreateProduct input, IProductRepository repo) =>
    repo.Update(id, input.Name, input.Price) is { } updated
        ? Results.Ok(updated)
        : Results.NotFound());

// DELETE /api/v1/products/{id}  → 204, or 404
products.MapDelete("/{id:int}", (int id, IProductRepository repo) =>
    repo.Delete(id) ? Results.NoContent() : Results.NotFound());

app.Run();
```

*What just happened:* Five endpoints, one shared prefix. Each handler follows the same rhythm - pull the inputs (route values, bound body, injected repo), call one repository method, return a `Results` matching the outcome:

- **List** - always `Results.Ok` with the collection. `200 OK`.
- **Get one** - the `is { } product` pattern means "if `Find` returned non-null, bind it to `product`." Found → `200 OK`; otherwise `404`.
- **Create** - `CreateProduct` is the input record from Phase 3, bound from the JSON body. `Results.Created(location, body)` returns `201 Created` *and* a `Location` header pointing at the new resource - the correct, polite REST answer to a successful POST.
- **Replace** - same null-check pattern: `200 OK` with the updated product, or `404` if that id never existed.
- **Delete** - `204 No Content` (success, nothing to return) or `404`.

> 💡 `Results.X` and `TypedResults.X` are siblings: `Results.Ok(x)` and `TypedResults.Ok(x)` do the same thing at runtime. `TypedResults` returns a *concrete, typed* result (`Ok<Product>` rather than the generic `IResult`), which makes endpoints easier to unit-test and lets the framework infer your response types for OpenAPI. When you start writing tests in [Phase 8](08-testing-and-production.md), prefer `TypedResults`.

### What about validation?

You don't repeat the validation logic here - it rides along from Phase 3. If `CreateProduct` carries data annotations (e.g. `[Required]` on `Name`, `[Range]` on `Price`) and you've wired up validation, a bad body is rejected with a `400` *before* your handler ever runs. The endpoint stays clean: by the time `repo.Add(input.Name, input.Price)` executes, the input is already known-valid. That's the whole point of binding and validation as their own phase - every endpoint inherits it for free.

## Driving it from the command line

Exercise all five with `curl`. Start the app, then in another terminal:

```bash
# List the seeded products
curl http://localhost:5000/api/v1/products
# → 200
# [{"id":1,"name":"Keyboard","price":49.99},{"id":2,"name":"Mouse","price":24.99}]

# Fetch one that exists
curl http://localhost:5000/api/v1/products/1
# → 200
# {"id":1,"name":"Keyboard","price":49.99}

# Fetch one that doesn't
curl -i http://localhost:5000/api/v1/products/999
# → HTTP/1.1 404 Not Found

# Create a new product
curl -i -X POST http://localhost:5000/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Monitor","price":199.99}'
# → HTTP/1.1 201 Created
# → Location: /api/v1/products/3
# {"id":3,"name":"Monitor","price":199.99}

# Replace it
curl -X PUT http://localhost:5000/api/v1/products/3 \
  -H "Content-Type: application/json" \
  -d '{"name":"Monitor 4K","price":249.99}'
# → 200
# {"id":3,"name":"Monitor 4K","price":249.99}

# Delete it
curl -i -X DELETE http://localhost:5000/api/v1/products/3
# → HTTP/1.1 204 No Content
```

*What just happened:* You walked the full lifecycle of a resource - create, read, update, delete - and each response carried a status code that *means something*: `200` for "here it is," `201 Created` plus a `Location` header for "made it, find it here," `204` for "done, nothing to say," `404` for "no such thing." A well-behaved REST client reads those codes; getting them right separates a real API from one that just happens to return JSON. (The `-i` flag tells `curl` to print response headers so you can see the status line and `Location`.)

## You just built the shape every framework shares

The endpoints you wrote contain almost no logic - they bind input, call one repository method, and pick a status code. **All the actual work lives behind the `IProductRepository` interface**, and the endpoints don't know or care what's behind it. Right now it's a `ConcurrentDictionary`. Tomorrow it's a database.

> 💡 That's the seam that makes this worth the ceremony. When you swap the in-memory store for an [EF Core](/guides/efcore-from-zero)-backed `ProductRepository` that talks to a real SQL database, **not one line of these five endpoints changes.** Write a new class implementing the same interface, change the one registration line, and the API keeps behaving identically - now with persistence. The endpoints were always coding against the contract, never the implementation - dependency injection earning its keep.

## Recap

- **A REST resource is five endpoints over one collection:** list, get-one, create, replace, delete - the same shape in every framework, expressed here with minimal APIs.
- **Group the resource** with `app.MapGroup("/api/v1/products")`, then hang `MapGet`/`MapPost`/`MapPut`/`MapDelete` off it so the path prefix is declared once.
- **Back it with a thread-safe store:** requests run concurrently, so use a `ConcurrentDictionary` + `Interlocked.Increment` (or a lock), not a plain `List` and `count++` - the bug only shows up under load.
- **Return typed `Results`/`TypedResults`** that carry meaning: `Ok` (200), `Created(location, body)` (201 with a `Location` header), `NoContent` (204), `NotFound` (404). Status codes are the contract.
- **Binding, validation, and DI come for free** from earlier phases - each handler just binds input, calls the repository, and picks a status. A bad body is rejected before your code runs.
- **The interface is the seam:** swap the in-memory repository for an EF Core one ([EF Core From Zero](/guides/efcore-from-zero)) and the endpoints don't change - only the registration line does.

## Quick check

```quiz
[
  {
    "q": "Why use a ConcurrentDictionary plus Interlocked.Increment for the in-memory store instead of a List and an int counter?",
    "choices": ["A List can't hold records", "Incoming requests run concurrently, so a non-thread-safe collection and counter can corrupt or hand out duplicate ids under load", "ConcurrentDictionary is required by MapGroup", "It makes the JSON serialize faster"],
    "answer": 1,
    "explain": "ASP.NET Core serves requests concurrently on multiple threads. A plain List and count++ work in single-request hand-testing but race and corrupt under real overlapping traffic - so the shared, mutated store must be thread-safe."
  },
  {
    "q": "What should a successful POST that creates a product return?",
    "choices": ["Results.Ok(product) - 200", "Results.NoContent() - 204", "Results.Created($\"/api/v1/products/{id}\", product) - 201 with a Location header", "Results.NotFound() - 404"],
    "answer": 2,
    "explain": "A create returns 201 Created with a Location header pointing at the new resource. Results.Created(location, body) sets both the status and the header - the correct REST response to a successful POST."
  },
  {
    "q": "You swap the in-memory ProductRepository for an EF Core-backed one. What has to change in the five endpoints?",
    "choices": ["Every handler must be rewritten to call the DbContext", "Nothing - the endpoints depend on IProductRepository, so only the new class and the registration line change", "MapGroup must be replaced with controllers", "The route templates must include the table name"],
    "answer": 1,
    "explain": "The endpoints code against the IProductRepository interface, never the concrete class. A new implementation plus changing the one registration line is all it takes - the handlers are untouched. That's the payoff of DI."
  }
]
```


---

# Authentication & Authorization

The products API works - full CRUD from Phase 6 - but right now anyone who finds the URL can delete every product. Fine for a demo, a disaster for anything real. This phase locks the writes while leaving the reads open.

Before any code, the one mental model that untangles this whole topic. People mix up two words constantly, and the framework keeps them strictly separate, so you should too.

**Authentication** answers *who are you?* - it looks at the request (a token, a cookie) and figures out the identity behind it. **Authorization** answers *what are you allowed to do?* - it takes that established identity and checks it against the rules on the endpoint.

> 📝 The shorthand that sticks: **authentication = who, authorization = what.** Authentication runs first and produces an identity; authorization runs second and judges it. You cannot check what someone may do until you know who they are - exactly why the middleware order in Phase 5 is non-negotiable.

Both are middleware, slotting into the pipeline you already know, right after routing and before your endpoints. Hold "who, then what," and everything below is detail hanging off it.

## Setting up JWT bearer authentication

For APIs, the standard approach is **JWT bearer authentication**. A JWT (JSON Web Token) is a signed blob of claims - "I am user 42, my role is Admin" - that the client sends on every request in the `Authorization: Bearer <token>` header. Your server doesn't store sessions; it verifies the signature and trusts the claims inside.

Register it in `Program.cs` before `builder.Build()`:

```csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = config["Jwt:Issuer"],
            ValidAudience = config["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(config["Jwt:Key"]!))
        };
    });

builder.Services.AddAuthorization();
```

*What just happened:* `AddAuthentication` registers the auth services and names the *default scheme* - the strategy used when an endpoint demands authentication. `AddJwtBearer` plugs in the JWT validator. `TokenValidationParameters` are the rules every incoming token must pass: signed by *our* key (`ValidateIssuerSigningKey` + `IssuerSigningKey`), from the issuer we expect, meant for our audience, and not expired (`ValidateLifetime`). If any check fails, the request arrives unauthenticated. `AddAuthorization()` then registers the services that enforce the *what*.

> ⚠️ That signing key is the master password of your entire auth system - anyone holding it can mint tokens that impersonate any user. It's read from `config["Jwt:Key"]` here, never hardcoded. In development use [user secrets](08-testing-and-production.md) or environment variables; in production use a secrets manager or vault. A signing key committed to source control is one of the most common, and most damaging, mistakes in real codebases.

## ⚠️ Wiring the middleware in the right order

Registering the *services* above isn't enough - you also have to add the *middleware* to the pipeline, and here Phase 5's ordering law comes back to collect its debt:

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

app.UseAuthentication();   // WHO is this? - must run first
app.UseAuthorization();    // WHAT may they do? - runs second

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

app.Run();
```

*What just happened:* `UseAuthentication` reads the token and establishes the identity; `UseAuthorization` then checks that identity against each endpoint's rules. Authentication **must** come before authorization - you can't judge permissions for someone you haven't identified yet - and **both must come before the endpoints they protect**. Get this backwards and the failure is silent: if `UseAuthorization` lands after your `MapGet`, the endpoint runs before anyone checks permissions, and your `[Authorize]` rules quietly do nothing. No error, no warning - just an open door. When auth misbehaves, this ordering is the first place to look.

## Protecting endpoints

With the plumbing in place, locking down an endpoint is a one-liner, in one of two flavors depending on style.

On a minimal-API endpoint or group, chain **`.RequireAuthorization()`**. On a controller or action, use the **`[Authorize]`** attribute. Both mean the same thing: "you must be authenticated to get past here."

Here's the products API with reads open and writes locked:

```csharp
var products = app.MapGroup("/products");

products.MapGet("/", () => Results.Ok(GetAll()));            // open to everyone
products.MapGet("/{id}", (int id) => Results.Ok(GetOne(id))); // open to everyone

products.MapPost("/", (Product p) => Results.Created($"/products/{p.Id}", p))
    .RequireAuthorization();                                  // login required

products.MapDelete("/{id}", (int id) => Results.NoContent())
    .RequireAuthorization();                                  // login required
```

*What just happened:* the two `GET`s stay public - anyone can browse the catalog. `POST` and `DELETE` carry `.RequireAuthorization()`, so a request without a valid token gets a `401 Unauthorized` before the handler runs. The authorization middleware enforces this; the handler never executes for an unauthenticated caller. If you'd protected the whole group instead, you could re-open a single endpoint with `.AllowAnonymous()` (or `[AllowAnonymous]` on a controller action), which punches an exception through a blanket rule.

### Policies and roles

"Logged in" is often too coarse. Deleting a product should require an *admin*, not just any authenticated user - that's a **policy**, a named rule you define once and apply by name.

Define it on `AddAuthorization`, then reference it where you protect the endpoint:

```csharp
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});

// ...later, on the endpoint:
products.MapDelete("/{id}", (int id) => Results.NoContent())
    .RequireAuthorization("AdminOnly");
```

*What just happened:* `AddPolicy` names a rule - `"AdminOnly"` requires the user to carry the `Admin` role (a role is just a claim in the token). Passing that name to `.RequireAuthorization("AdminOnly")` upgrades the gate: now an authenticated *non*-admin gets a `403 Forbidden` (known, but not permitted), while an admin sails through. Note the two different rejections - `401` means "I don't know who you are," `403` means "I know exactly who you are, and the answer is no."

### Reading the user's claims

Inside a handler you often need *who* is calling - to stamp an audit field, or filter to their own data. The identity established by authentication lives on `HttpContext.User`, a `ClaimsPrincipal`. In a minimal API you get it by declaring a `ClaimsPrincipal` parameter, and the framework injects it:

```csharp
products.MapPost("/", (Product p, ClaimsPrincipal user) =>
{
    var createdBy = user.FindFirst(ClaimTypes.Name)?.Value ?? "unknown";
    p.CreatedBy = createdBy;
    return Results.Created($"/products/{p.Id}", p);
}).RequireAuthorization();
```

*What just happened:* declaring `ClaimsPrincipal user` in the parameter list tells the framework to hand you the authenticated identity (the same object as `HttpContext.User`). `user.FindFirst(ClaimTypes.Name)` pulls a single claim out of the token - here the username - so we can record who created the product. Every claim the token carried is readable this way; authorization already guaranteed the user is real before the handler ran.

## Where tokens come from

So far we've *consumed* tokens. Someone has to *issue* them: a **login endpoint** verifies a username and password, then builds and signs a JWT with the user's claims and hands it back. The client stores that token and sends it on every subsequent request.

```csharp
app.MapPost("/login", (LoginRequest req) =>
{
    // ... verify credentials against your user store ...
    var claims = new[] { new Claim(ClaimTypes.Name, req.Username) };
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]!));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: config["Jwt:Issuer"],
        audience: config["Jwt:Audience"],
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: creds);

    return Results.Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}).AllowAnonymous();
```

*What just happened:* the endpoint builds a `JwtSecurityToken` carrying the user's claims, signs it with the *same key* the validator checks against, and serializes it to a string with `JwtSecurityTokenHandler`. It's `.AllowAnonymous()` because you can't require a token from the endpoint whose job is to *hand out* tokens. Notice the symmetry: this endpoint signs with `Jwt:Key` and the `AddJwtBearer` setup validates with `Jwt:Key` - the shared secret ties issuing and verifying together. (Newer code may reach for `JsonWebTokenHandler`; the idea is identical.)

> 💡 Verifying passwords, hashing them safely, storing users, handling registration and password resets - that's a lot of security-sensitive code you do not want to write by hand. **ASP.NET Core Identity** is the batteries-included system for exactly this: user stores, password hashing, lockout, the works. The hand-rolled login above shows the *mechanics* so the model is clear, but for a real app, lean on Identity for user management and JWT bearer (what we built) for API protection.

## Recap

- **Authentication = who, authorization = what.** Authentication runs first and establishes an identity; authorization runs second and checks that identity against endpoint rules. Both are middleware.
- **JWT bearer** is the standard for APIs: `AddAuthentication(...).AddJwtBearer(...)` plus `AddAuthorization()`, with `TokenValidationParameters` defining which tokens are trusted (issuer, audience, lifetime, signing key). Keep the signing key in config/secrets, never in source.
- **Order is the law:** `app.UseAuthentication()` before `app.UseAuthorization()`, and both before the endpoints they protect. Get it wrong and `[Authorize]` silently does nothing.
- **Protect endpoints** with `.RequireAuthorization()` (minimal API) or `[Authorize]` (controllers); re-open exceptions with `.AllowAnonymous()` / `[AllowAnonymous]`. Use named **policies** (`AddPolicy` + `RequireRole`) for finer rules. `401` = unauthenticated, `403` = authenticated but forbidden.
- **Read the caller** via `ClaimsPrincipal` (inject it into a handler, or use `HttpContext.User`). A login endpoint issues signed JWTs; **ASP.NET Core Identity** is the full user-management option.

Quick gut-check before moving on:

```quiz
[
  {
    "q": "What is the difference between authentication and authorization?",
    "choices": [
      "Authentication checks permissions; authorization verifies identity",
      "Authentication verifies WHO you are; authorization checks WHAT you're allowed to do",
      "They are two names for the same middleware",
      "Authentication is for APIs; authorization is for web pages"
    ],
    "answer": 1,
    "explain": "Authentication establishes identity (who), then authorization checks that identity against permissions (what). Authentication must run first."
  },
  {
    "q": "In Program.cs, which order is correct?",
    "choices": [
      "UseAuthorization() before UseAuthentication()",
      "UseAuthentication() before UseAuthorization(), both before the endpoints",
      "Endpoints first, then UseAuthentication() and UseAuthorization()",
      "Order doesn't matter for auth middleware"
    ],
    "answer": 1,
    "explain": "You must establish identity before checking permissions, and both must run before the endpoints they protect - otherwise [Authorize] rules silently do nothing."
  },
  {
    "q": "An authenticated non-admin user calls an endpoint protected with .RequireAuthorization(\"AdminOnly\"). What do they get?",
    "choices": [
      "401 Unauthorized - they aren't logged in",
      "200 OK - being logged in is enough",
      "403 Forbidden - they're known, but not permitted",
      "500 Internal Server Error"
    ],
    "answer": 2,
    "explain": "401 means unauthenticated (we don't know who you are). 403 means authenticated but lacking the required role/policy - known, but not permitted."
  }
]
```


---

# Testing & Production

You've grown the products API from a single endpoint into a real REST service with validation, dependency injection, a middleware pipeline, and JWT auth. Now comes the part that decides whether anyone trusts it: proving it works, and running it somewhere real without it falling over at 3am. Both turn out to be small once you see the one fact that makes them small.

## The mental model: integration testing runs the whole app in memory

Here's the thing that makes ASP.NET Core genuinely pleasant to test. There's a class - `WebApplicationFactory<TEntryPoint>`, from the `Microsoft.AspNetCore.Mvc.Testing` package - whose entire job is to **start your real application in memory** and hand you an `HttpClient` wired straight into it.

> 💡 An integration test is nothing more than: spin up your app in-process, ask the factory for an `HttpClient`, and make requests as if you were a caller out on the network. Except there *is* no network - no real socket, no port, no Kestrel listening, no `dotnet run` in another terminal. The request travels the **entire pipeline** - middleware, routing, model binding, your endpoint, the lot - exactly as it would in production, but never leaves the process. It runs in milliseconds.

That's the whole idea: you're not mocking the framework or testing one method in isolation, you're exercising the assembled app the way a real client would, and reading back what it returns.

xUnit is the common test framework in .NET (`dotnet new xunit` scaffolds a project), and `WebApplicationFactory` plugs into it through `IClassFixture<T>` - xUnit's way of building one expensive thing once and sharing it across the tests in a class. Here's a test against the products API:

```csharp
public class ProductsApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    public ProductsApiTests(WebApplicationFactory<Program> factory) => _client = factory.CreateClient();

    [Fact]
    public async Task Get_products_returns_ok()
    {
        var res = await _client.GetAsync("/api/v1/products");
        Assert.True(res.IsSuccessStatusCode);
    }
}
```

*What just happened:* `IClassFixture<WebApplicationFactory<Program>>` tells xUnit to construct the factory once and inject it into the constructor. `factory.CreateClient()` boots the app in memory and gives back an `HttpClient` pointed at it. The `[Fact]` is one test; inside it we `GET /api/v1/products` and assert a success status. That single `GetAsync` ran the whole app - middleware pipeline, routing, the handler pulling products out of DI - and came back, all without a port ever opening. To check the body too, add `var products = await res.Content.ReadFromJsonAsync<List<Product>>();` and assert on the shape.

> ⚠️ For the test project to reference `Program`, your minimal-API `Program.cs` needs one extra line at the very end:
>
> ```csharp
> public partial class Program { }
> ```
>
> Top-level statements (the `var builder = WebApplication.CreateBuilder(args);` style you've used all guide) compile into an `internal` class named `Program` by default - which a separate test project can't see. Adding `public partial class Program { }` makes it `public` so `WebApplicationFactory<Program>` can find your entry point. Forget this and you get a confusing "`Program` is inaccessible due to its protection level" error; this is the fix.

This is the heart of testing an ASP.NET Core app. Wiring it into CI so it runs on every push - and the general discipline of test layers, fixtures, and pipelines - is covered in [testing in CI](/guides/testing-in-ci).

## Overriding services: swap in a fake repository for tests

The test above hit the real DI container, using whatever `Program.cs` registered for the product store. For a fast, deterministic test you usually don't want the real database - you want a fake or in-memory implementation instead. `WebApplicationFactory` lets you reconfigure the container before the app starts, through `WithWebHostBuilder`:

```csharp
[Fact]
public async Task Get_products_uses_the_fake_store()
{
    var client = _factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureServices(services =>
        {
            services.RemoveAll<IProductRepository>();
            services.AddSingleton<IProductRepository, FakeProductRepository>();
        });
    }).CreateClient();

    var res = await client.GetAsync("/api/v1/products");
    var products = await res.Content.ReadFromJsonAsync<List<Product>>();

    Assert.Single(products);
}
```

*What just happened:* `WithWebHostBuilder` gives you a chance to run extra configuration *after* `Program.cs` has registered everything but *before* the app starts handling requests. We `RemoveAll<IProductRepository>()` to drop whatever the real app registered, then `AddSingleton` a `FakeProductRepository` we control - perhaps seeded with a single known product. Now the endpoint, routing, and pipeline are all real, but the data source is a fake we can make assertions against. Because the override happens last, it wins - the standard trick for testing against an in-memory store instead of a live database.

> 📝 Not every test needs the factory. A `WebApplicationFactory` test is an *integration* test - it runs the HTTP pipeline. If you only want to test the logic inside one service or repository - say, a `ProductService` method that calculates a discount - that's a plain **unit test**: `new` up the class (passing a fake repository to its constructor), call the method, assert the result. No factory, no `HttpClient`, no pipeline. Reach for the factory when testing *the app*; reach for a unit test when testing *a piece*.

## Configuration: appsettings, environments, and `IConfiguration`

A test database is one example of "this differs between environments." Configuration is how ASP.NET Core handles all of them, as a stack of layers that override each other.

When your app starts, the builder reads configuration from several sources and merges them, with later sources winning over earlier ones:

1. `appsettings.json` - base settings, committed to the repo.
2. `appsettings.{Environment}.json` - environment-specific overrides, e.g. `appsettings.Development.json` or `appsettings.Production.json`.
3. **Environment variables** - what the host or container injects.
4. **User secrets** - local-only secrets in development (never committed), for things like a JWT signing key you don't want in source control.

The "{Environment}" piece is driven by one environment variable: **`ASPNETCORE_ENVIRONMENT`**, conventionally `Development`, `Staging`, or `Production`. Set it to `Production` and ASP.NET Core layers `appsettings.Production.json` on top of `appsettings.json`; leave it at `Development` and you get `appsettings.Development.json` instead. That's how the *same build* picks up different settings in different places.

You read merged values through `IConfiguration`, which the builder exposes as `builder.Configuration`:

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

// A single value, by key (":" walks into nested JSON):
var connectionString = builder.Configuration["ConnectionStrings:Products"];

// Or bind a whole section to a typed options object:
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));

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

*What just happened:* `builder.Configuration["ConnectionStrings:Products"]` pulls one value out of the merged configuration - the colon walks into nested JSON, so it reads `{ "ConnectionStrings": { "Products": "..." } }`. The value comes from *whichever layer set it last*: it might live in `appsettings.json` for local dev but be overridden by an environment variable in production, and your code doesn't change either way. `Configure<JwtOptions>(...GetSection("Jwt"))` binds the whole `"Jwt"` block to a strongly-typed `JwtOptions` class, injectable anywhere via `IOptions<JwtOptions>` - the same JWT settings from the auth phase, now sourced from config instead of hardcoded. The rule that makes this all work: **base file for defaults, environment file for per-environment overrides, environment variables and secrets for the things that change per deploy or must stay out of source control.**

## Production: publish, Kestrel, and a small Docker image

You've been running with `dotnet run`, which compiles and launches in one step - perfect for development, not production. For a real deploy, produce an optimized build with `dotnet publish`:

```bash
dotnet publish -c Release -o ./publish
```

*What just happened:* `-c Release` builds in **Release** configuration - optimizations on, debug symbols and dev-time checks off - instead of the default `Debug`. `-o ./publish` drops the result, your DLLs plus everything needed to run, into a `publish` folder. Launch it with `dotnet ./publish/ProductsApi.dll`. This is the artifact you ship, not your source tree.

Inside that artifact runs **Kestrel** - the cross-platform, high-performance web server built into ASP.NET Core. It's already what served your requests during `dotnet run`; in production it's the thing actually listening for connections. Kestrel is fast and perfectly capable of facing the internet, but the common, recommended shape is to put a **reverse proxy** in front of it - nginx, IIS, or YARP. The proxy terminates TLS (handles HTTPS), can load-balance across multiple instances of your app, and shields Kestrel from the rough edges of the public internet. Your app speaks plain HTTP to the proxy; the proxy speaks HTTPS to the world.

The cleanest way to package all of this is a **multi-stage Docker image**: one stage with the full .NET SDK to build and publish, a second tiny stage with only the runtime to run.

```bash
# Build stage - has the full SDK
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

# Run stage - just the runtime, much smaller
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_ENVIRONMENT=Production
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "ProductsApi.dll"]
```

*What just happened:* the first stage uses the `sdk` image - the whole toolchain - to `dotnet publish` your Release build into `/app`. The second stage starts from the much smaller `aspnet` runtime image (it has the .NET runtime but not the compilers and build tools you no longer need), and copies *only* the published output from the build stage with `COPY --from=build`. The result is a leaner image with a smaller attack surface - you're not shipping the SDK to production. `ASPNETCORE_ENVIRONMENT=Production` makes the app layer in `appsettings.Production.json` and turn off developer conveniences like the detailed exception page; `ASPNETCORE_URLS` tells Kestrel which address and port to bind. Configuration that differs per environment - connection strings, the JWT key, the real database URL - comes in as environment variables, the same layering from the previous section, so the *image stays identical* across staging and production.

That's the full deploy shape: publish a Release build, run it on Kestrel inside a small multi-stage container, configure it through environment variables, and put a TLS-terminating reverse proxy in front. Taking it the rest of the way to a live URL - host, CI, domain and certificate specifics - is covered in [ship your side project](/guides/ship-your-side-project).

## Recap

- **Integration tests run the whole app in memory.** `WebApplicationFactory<Program>` from `Microsoft.AspNetCore.Mvc.Testing` boots your real app in-process and gives you an `HttpClient` via `CreateClient()` - full pipeline, no network, no port. Use it with xUnit's `IClassFixture<T>`.
- Your minimal-API `Program.cs` must end with `public partial class Program { }` so the test project can reference the entry point - otherwise `Program` is `internal` and inaccessible.
- **Override services for tests** with `factory.WithWebHostBuilder(b => b.ConfigureServices(...))` - `RemoveAll<T>()` the real registration and add a fake/in-memory one. Unit tests of a single service or repository need no factory; just `new` it up with a fake dependency.
- **Configuration layers**: `appsettings.json` → `appsettings.{Environment}.json` → environment variables → user secrets, later sources winning. The environment comes from `ASPNETCORE_ENVIRONMENT`. Read values with `builder.Configuration["Key"]` or bind a section to typed options.
- **Production**: `dotnet publish -c Release`, run on **Kestrel** behind a reverse proxy (nginx/IIS/YARP) that terminates TLS, packaged as a **multi-stage Docker image** (`sdk` to build, `aspnet` runtime to run), with config supplied via environment variables.

## Quick check

Lock in the core fact (in-memory testing) and the two production must-knows:

```quiz
[
  {
    "q": "How does WebApplicationFactory<Program> let you test an ASP.NET Core app without a real port?",
    "choices": ["It mocks every endpoint so no real code runs", "It starts your real app in memory and gives you an HttpClient wired straight into the full pipeline", "It launches Kestrel on a random free port in the background", "It only works for unit tests of individual services"],
    "answer": 1,
    "explain": "The factory boots the actual application in-process and hands back an HttpClient. Requests travel the entire pipeline - middleware, routing, binding, your endpoint - but never leave the process, so there's no socket or port involved."
  },
  {
    "q": "Why must a minimal-API Program.cs end with `public partial class Program { }` for integration tests?",
    "choices": ["It registers the test framework", "Top-level statements compile to an internal Program class, so the test project can't reference it until you make it public", "It enables Release-mode optimizations", "It starts the Kestrel server"],
    "answer": 1,
    "explain": "Top-level statements generate an internal Program by default. WebApplicationFactory<Program> needs to reference that type from a separate project, so you add `public partial class Program { }` to make it public."
  },
  {
    "q": "Which environment variable selects which appsettings.{Environment}.json file is layered on top of appsettings.json?",
    "choices": ["DOTNET_ENV", "ASPNETCORE_URLS", "ASPNETCORE_ENVIRONMENT", "NODE_ENV"],
    "answer": 2,
    "explain": "ASPNETCORE_ENVIRONMENT (Development/Staging/Production) drives which environment-specific appsettings file is merged in. ASPNETCORE_URLS sets the bind address, not the environment."
  }
]
```


---

# Where to Go Next

Take stock of what you can actually do now. You can stand up a minimal API server, route requests with path and query parameters, group routes, bind and validate request bodies into types, register services and pull them in through dependency injection, write your own middleware and place it correctly in the pipeline, build full CRUD with proper status codes, lock endpoints behind JWT auth, and prove the whole thing works with `WebApplicationFactory` integration tests before shipping. That's a real REST API, not a toy.

And here's the quieter win. You didn't only learn a framework - you internalized the two pillars it's built on. A request flows through a **middleware pipeline**, an ordered chain where each piece can act on the way in and the way back out. Your code receives its collaborators through **dependency injection** instead of newing them up. Hold those two ideas and the rest of ASP.NET Core - routing, binding, auth, caching - is detail hanging off them. That's why you can reason about the framework when something misbehaves at 2am instead of guessing.

So this last phase isn't more endpoints. It's the map: one design choice you'll meet on every project, the ecosystem around the framework, the roots underneath it, and one concrete thing to go build.

## Minimal APIs vs controllers

Everything in this guide used **minimal APIs** - `MapGet`, `MapPost`, lambdas, lean and direct. A genuinely good default for a focused API. But you'll also meet the older, heavier sibling: **MVC controllers**, classes decorated with `[ApiController]` and attribute routing. Both run on the exact same pipeline and DI container you already understand. This isn't two frameworks - it's two front doors into one.

```mermaid
flowchart TD
  Start[Building an API?] --> Size{App size and team?}
  Size -- Small, focused, solo --> Min[Minimal APIs]
  Size -- Large app or bigger team --> Ctrl[Controllers]
  Min --> Pipe[Same pipeline plus DI]
  Ctrl --> Pipe
```

What controllers buy you as an app grows: automatic model validation (an invalid body returns a `400` with no manual checks), filters for cross-cutting concerns, and conventions that keep a large codebase consistent across a team. A controller action looks like this:

```csharp
[ApiController]
[Route("products")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _service;
    public ProductsController(IProductService service) => _service = service;

    [HttpGet("{id:int}")]
    public IActionResult Get(int id)
    {
        var product = _service.Find(id);
        return product is null ? NotFound() : Ok(product);
    }
}
```

Notice it's the same constructor injection and the same `IProductService` from Phase 4 - only the shape around it changed.

> 💡 You don't have to choose all-or-nothing: you can mix minimal APIs and controllers in the same app. Pick by size and team - minimal APIs for small, focused services; controllers when an app or team grows large enough to want the structure and automatic validation. Neither is "more advanced" - they're aimed at different sizes of problem.

## The .NET web ecosystem

Your API doesn't live alone. A few neighbors you'll reach for next:

- **EF Core - the data layer.** Every example in this guide stored products in memory, which vanishes on restart. Real services persist. [EF Core](/guides/efcore-from-zero) is .NET's default ORM: define your `Product` as an entity, point it at SQLite or Postgres, and create/read/update/delete calls become real database operations. Here's the payoff from Phase 6 keeping the repository separate from the endpoints - swap the in-memory store for an EF Core one and the endpoints don't change at all. For lighter, hand-written SQL instead of a full ORM, **Dapper** maps query results to objects with very little ceremony.
- **Blazor - a C# front-end.** If you'd rather not jump to a JavaScript framework, [Blazor](/guides/blazor-from-zero) lets you build interactive web UIs in C#, sharing models with your API. Or pair this API with React, Vue, or anything else - it's just HTTP.
- **SignalR and gRPC - beyond request/response.** When you need real-time push (chat, live dashboards, notifications), **SignalR** gives you websockets without the plumbing. When you need fast, typed service-to-service calls, **gRPC** (`Grpc.AspNetCore`) is the tool. Both sit on the same host you already know.

📝 None of these is mandatory, and none replaces what you learned. They're layers you add when a project needs them - each slotting into the pipeline-and-DI model you already carry.

## The roots underneath

For the deepest understanding - the kind that makes performance tuning and weird production bugs make sense - go down a level. [The ASP.NET Pipeline & Kestrel](/guides/the-aspnet-pipeline-and-kestrel) takes apart the web server that actually accepts the socket connection and the request pipeline that everything in this guide rode on. You've been using both the whole time; this guide shows you their gears. It's optional, but it's where "I use ASP.NET Core" becomes "I understand ASP.NET Core."

## What to build

Reading more won't make this stick. Finishing one real thing will. Here's the assignment, deliberately concrete.

Take the **products API** you grew across this guide and carry it all the way home:

- **Swap the in-memory repository for EF Core + a real database** so products survive a restart. Endpoints stay; the store changes. ([EF Core From Zero](/guides/efcore-from-zero) walks the persistence part.)
- **Add JWT auth or ASP.NET Core Identity** so each request proves who it is - the Phase 7 pattern, applied for real.
- **Generate OpenAPI/Swagger docs** (built-in OpenAPI or `Swashbuckle`) so other people, and future you, can read the contract.
- **Wire up logging and a little observability** to see what the service does in production.
- **Add output caching or rate limiting** with the built-in middleware when ready - both drop straight into the pipeline.
- **Deploy it** somewhere you can hit from your phone.

If the products API feels too familiar, build something small and new end to end instead - a URL shortener or a notes API. Same muscles: routes, binding, a service, middleware, auth, tests, deploy. Finishing one project completely teaches more than three more tutorials would.

Here's the clear-eyed close. ASP.NET Core was never magic. A request travels a **middleware pipeline** to an endpoint, and your code gets its services handed to it through **dependency injection**. That's the whole framework, and you understand it now. Go give the products API a real database, lock it behind auth, deploy it, and show someone. You're ready.

## Recap

1. **You can ship a real ASP.NET Core API** - minimal APIs, routing, binding and validation, DI, custom middleware, full CRUD, JWT auth, and integration tests - and understand *why* each piece works, because the two pillars are clear.
2. **Minimal APIs vs controllers is a size choice, not a skill ladder** - both run on the same pipeline and DI; minimal APIs for focused services, controllers for larger apps/teams (automatic `400`, filters, conventions). You can mix them.
3. **The ecosystem layers onto what you know** - EF Core (or Dapper) for data, Blazor for a C# UI, SignalR for real-time, gRPC for service-to-service - each slotting into pipeline + DI.
4. **The roots are there when you want them** - the pipeline and Kestrel turn "I use it" into "I understand it."
5. **Build and finish one thing** - carry the products API to EF Core + a real DB, real auth, Swagger, logging, caching, and a deploy. Or build a small URL shortener / notes API end to end.

## Quick check

Three decisions to take with you as you leave this guide:

```quiz
[
  {
    "q": "Your team is starting a large API and wants automatic 400-on-invalid, filters, and consistent conventions across many endpoints. Which approach fits best?",
    "choices": [
      "Minimal APIs, always, regardless of size",
      "MVC controllers, which add automatic model validation, filters, and conventions for larger apps and teams",
      "A completely different framework, since ASP.NET Core can't do this",
      "gRPC, because it's the only option for large apps"
    ],
    "answer": 1,
    "explain": "Controllers ([ApiController] + attribute routing) give automatic model validation, filters, and conventions that suit larger apps and teams. Minimal APIs are leaner and great for focused services. Both run on the same pipeline, and you can even mix them."
  },
  {
    "q": "You replace the Phase 6 in-memory repository with an EF Core + database one. What mostly changes?",
    "choices": [
      "Every endpoint must be rewritten from scratch",
      "Only the repository swaps to EF Core; the endpoints stay the same because the data access was kept separate",
      "You must abandon minimal APIs and move to controllers",
      "Nothing - ASP.NET Core persists data to a database automatically"
    ],
    "answer": 1,
    "explain": "Because Phase 6 kept the repository separate from the endpoints, swapping the in-memory store for an EF Core one leaves the endpoints unchanged. You replace the bottom layer, not the top."
  },
  {
    "q": "Which pairing of tool to job is correct in the .NET web ecosystem?",
    "choices": [
      "Blazor for service-to-service RPC, SignalR for the database",
      "EF Core (or Dapper) for data, Blazor for a C# front-end, SignalR for real-time, gRPC for service-to-service",
      "gRPC for building the UI, EF Core for websockets",
      "Dapper for real-time push, SignalR for object-relational mapping"
    ],
    "answer": 1,
    "explain": "EF Core is the default ORM (Dapper for lightweight SQL mapping), Blazor builds C# web UIs, SignalR handles real-time/websockets, and gRPC handles typed service-to-service calls. Each layers onto the same pipeline + DI model."
  }
]
```
