# Exercise: Northwind Web API

Build Northwind's products API. Write an async controller with GET (all, by category, by id), POST, PUT and DELETE, each answering the right status code, and set up Program.cs to register the DAO and handle errors in one place.

Journeyman · ASP.NET Core · REST API · Updated November 2, 2026

Northwind's products need an API, so other programs, such as a mobile app or a web page's JavaScript, can read and change them. The model and the Dapper DAO are written. You'll write the controller, and set up `Program.cs`.

## Get the exercise code

Open this lesson's GitHub repository, [github.com/ask-codey/exercise-northwind-web-api](https://github.com/ask-codey/exercise-northwind-web-api), also linked from the **Exercise** box beside the lesson. Click **Code**, then **Download ZIP**, and unzip it into your `bootcamp` folder. Or clone it with Git. Then open `NorthwindApi.slnx` in Visual Studio.

| File | What's in it |
|---|---|
| `NorthwindApi/Models/Product.cs` | A product, with its validation rules |
| `NorthwindApi/Data/IProductDao.cs`, `ProductDao.cs` | The products table, with async Dapper methods |
| `NorthwindApi/Errors/DatabaseConflictHandler.cs` | Answers SQL Server's constraint errors with `409 Conflict` |
| `NorthwindApi/Controllers/ProductsController.cs` | For you to finish. Its requirements are in the comment at the top. |
| `NorthwindApi/Program.cs` | For you to finish, where the `TODO` comments are |
| `NorthwindApi/NorthwindApi.http` | Ten numbered requests, each with the answer to expect |
| `NorthwindApi.Tests` | Tests that start the API in memory, with a fake DAO |

The API uses your own **Northwind** database. If yours has another name, change the connection string in `appsettings.json`.

## What to do

**`Program.cs`:**

1. Register `ProductDao` as the `IProductDao`, with the connection string.
2. Add problem details, and register the `DatabaseConflictHandler`.
3. Handle exceptions for the whole API.

**`ProductsController`**, every action async:

| Request | Answers |
|---|---|
| `GET /api/products` | `200` with every product |
| `GET /api/products?categoryId=1` | `200` with that category's products |
| `GET /api/products/1` | `200` with the product, or `404` |
| `POST /api/products` | `201` with the new product and its address, or `400` for an invalid one |
| `PUT /api/products/1` | `200` with the updated product, or `404` |
| `DELETE /api/products/1` | `204`, or `404` |

Run the tests with **Test > Run All Tests**. Then run the API with **Ctrl+F5**, open `NorthwindApi.http`, and send its requests in order.

## Hints

- **An optional filter** from the query string is a nullable parameter: `GetAll(int? categoryId)`. The DAO already knows what to do with `null`.
- **`{id:int}`** in each route template matches whole numbers only.
- **`CreatedAtAction(nameof(GetById), new { id = added.ProductId }, added)`** answers `201`, with the `Location` header.
- **You don't need to check `ModelState`:** `[ApiController]` answers `400` for an invalid product before your action runs.
- **The tests use a fake DAO** with three products, swapped in for `ProductDao`. That works because the controller asks for an `IProductDao`, not a `ProductDao`: the interface from *Abstraction and Interfaces* paying off.
- **Product 666** makes the fake DAO throw. Your API should answer `500` problem details, without showing the exception's message.

## When you're done

Every test passes, and the `.http` file's requests answer as their comments say. The last one is the best: deleting Chai, which orders still use, answers:

```http
HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.10",
  "title": "Conflict",
  "status": 409,
  "detail": "Other data uses this item, or it uses data that doesn't exist, so it can't be saved or deleted."
}
```

A database error, turned into a clear answer, without a single `try` in your controller.

## Submit your exercise

The exercise is how you finish this lesson. When your work is done, put it on GitHub in a repo of your own, then submit that repo's address here.

1. On GitHub, click **+**, then **New repository**. Name it (for example `exercise-northwind-web-api`), choose **Public**, and click **Create repository**. Leave out the README: your folder already has one.
2. In a terminal in your exercise folder (Git Bash on Windows), send your work to your new repo. If you downloaded the ZIP:

   ```bash
   git init -b main
   git add .
   git commit -m "My solution"
   git remote add origin https://github.com/your-name/exercise-northwind-web-api.git
   git push -u origin main
   ```

   If you cloned the starter instead, point your copy at your own repo, then commit and push:

   ```bash
   git remote set-url origin https://github.com/your-name/exercise-northwind-web-api.git
   git add .
   git commit -m "My solution"
   git push -u origin main
   ```

3. Paste your repo's address, such as `https://github.com/your-name/exercise-northwind-web-api`, into **Submit your exercise** on this page. The site checks that the repo is public and holds your commits, and then the lesson is complete.

Changed something after you submitted? Push it, and submit again: your latest submission is the one that counts.

## So what

That's a complete, professional API: REST addresses, honest status codes, async all the way down, validation for free, and errors handled in one place. The next chapter writes the other side: a C# program that calls it.

## Key terms

- [REST](https://www.ask-codey.com/glossary/rest)
- ActionResult&lt;T>
- [Async/Await](https://www.ask-codey.com/glossary/async-await)
- Exception Handling Middleware

## Related

- [Returning ActionResult&lt;T>](https://www.ask-codey.com/lessons/web-apis-with-controllers-returning-actionresult-t)
- [Handling Global Exceptions in ASP.NET Core](https://www.ask-codey.com/lessons/handling-global-exceptions-in-aspnet-core)
- [Tasks, async and await](https://www.ask-codey.com/lessons/async-and-await-in-csharp-tasks-async-and-await)

Source: https://www.ask-codey.com/lessons/exercise-northwind-web-api
