From 4a55bdab3b8a220841a0a10598c424188fc5b091 Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:00:59 +0000
Subject: [PATCH 1/6] Bring the docs up to date with review text, review search
and the new MCP features
The sample movies have recognisable titles now, so every example shows the
new ids and numbers. Reviews have text and can be searched by it and by a
minimum star rating. MCP has two more prompts, a top rated resource, id
completions, resource links, icons, null fields and an optional chat demo.
dotnet run listens on port 8080 now, not 5032.
---
src/content/docs/graphql/getting-started.mdx | 18 +++----
.../docs/graphql/guides/add-a-field.mdx | 8 +--
.../docs/graphql/guides/run-locally.mdx | 12 ++---
.../docs/graphql/guides/secure-the-api.mdx | 2 +-
.../docs/graphql/reference/configuration.mdx | 2 +-
src/content/docs/graphql/reference/schema.mdx | 23 +++++----
src/content/docs/lambda/getting-started.mdx | 11 ++--
.../docs/lambda/guides/add-a-function.mdx | 2 +-
.../docs/lambda/guides/run-locally.mdx | 2 +-
.../docs/lambda/reference/functions.mdx | 44 +++++++++-------
.../docs/mcp/explanation/decisions.mdx | 2 +-
src/content/docs/mcp/getting-started.mdx | 15 +++---
src/content/docs/mcp/guides/add-a-tool.mdx | 12 ++---
.../docs/mcp/guides/connect-a-client.mdx | 16 +++---
src/content/docs/mcp/guides/run-locally.mdx | 33 ++++++++----
.../docs/mcp/guides/secure-the-server.mdx | 2 +-
src/content/docs/mcp/index.mdx | 2 +-
.../docs/mcp/reference/configuration.mdx | 2 +-
.../mcp/reference/resources-and-prompts.mdx | 25 ++++++---
src/content/docs/mcp/reference/tools.mdx | 27 ++++++----
.../docs/minimalapi/getting-started.mdx | 12 ++---
.../minimalapi/guides/add-an-endpoint.mdx | 10 ++--
.../docs/minimalapi/guides/run-locally.mdx | 12 ++---
.../docs/minimalapi/guides/secure-the-api.mdx | 2 +-
.../minimalapi/reference/configuration.mdx | 2 +-
.../docs/minimalapi/reference/endpoints.mdx | 51 +++++++++++++------
src/content/docs/webapi/getting-started.mdx | 10 ++--
.../docs/webapi/guides/add-an-endpoint.mdx | 10 ++--
.../docs/webapi/guides/run-locally.mdx | 6 +--
.../docs/webapi/reference/endpoints.mdx | 49 +++++++++++++-----
30 files changed, 254 insertions(+), 170 deletions(-)
diff --git a/src/content/docs/graphql/getting-started.mdx b/src/content/docs/graphql/getting-started.mdx
index 86e3d30..993ed04 100644
--- a/src/content/docs/graphql/getting-started.mdx
+++ b/src/content/docs/graphql/getting-started.mdx
@@ -55,11 +55,11 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open Nitro, the GraphQL IDE, at [http://localhost:5032/graphql](http://localhost:5032/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
+5. Open Nitro, the GraphQL IDE, at [http://localhost:8080/graphql](http://localhost:8080/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
```graphql
{
- movies(query: "global", first: 2) {
+ movies(query: "space", first: 2) {
totalCount
pageInfo { hasNextPage endCursor }
nodes { id title reviewCount averageStars }
@@ -70,8 +70,8 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or ask from the terminal.
```bash
- curl -X POST "http://localhost:5032/graphql" -H "Content-Type: application/json" \
- -d '{"query":"{ movies(query: \"global\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
+ curl -X POST "http://localhost:8080/graphql" -H "Content-Type: application/json" \
+ -d '{"query":"{ movies(query: \"space\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
```
Either way you get a page of movies back:
@@ -80,18 +80,18 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
{
"data": {
"movies": {
- "totalCount": 2,
- "pageInfo": { "hasNextPage": false, "endCursor": "b2Zmc2V0OjI=" },
+ "totalCount": 4,
+ "pageInfo": { "hasNextPage": true, "endCursor": "b2Zmc2V0OjI=" },
"nodes": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
]
}
}
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
7. Run the tests while you're here.
diff --git a/src/content/docs/graphql/guides/add-a-field.mdx b/src/content/docs/graphql/guides/add-a-field.mdx
index 9e5ea2a..d93bb46 100644
--- a/src/content/docs/graphql/guides/add-a-field.mdx
+++ b/src/content/docs/graphql/guides/add-a-field.mdx
@@ -60,7 +60,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```bash
dotnet run --project ./src/Presentation
- curl "http://localhost:5032/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
+ curl "http://localhost:8080/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
```
The diff is the one field, described:
@@ -117,7 +117,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```graphql
{
- movie(id: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593") {
+ movie(id: "cb188ef6-14e7-f85f-2f78-924c2ab26b28") {
title
bestReview { stars author { firstName lastName } }
}
@@ -125,10 +125,10 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```
```json
- { "data": { "movie": { "title": "Assimilated disintermediate adapter", "bestReview": { "stars": 5, "author": { "firstName": "Jana", "lastName": "Auer" } } } } }
+ { "data": { "movie": { "title": "Deep Space Gardeners", "bestReview": { "stars": 5, "author": { "firstName": "Caroline", "lastName": "Bins" } } } } }
```
- Jana gave it five stars. Jana's average is 2.6, so this is high praise.
+ Caroline gave it five stars, and three in a second review. Caroline contains multitudes.
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 2044e00..b0f362b 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:5032](http://localhost:5032) and `https://localhost:7032`.
+ `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
```bash
@@ -66,9 +66,9 @@ Nitro is the easy way: it knows the schema, autocompletes, and keeps your query
```graphql
# One movie, its reviews and who wrote them
{
- movie(id: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593") {
+ movie(id: "cb188ef6-14e7-f85f-2f78-924c2ab26b28") {
title
- reviews { stars author { firstName lastName } }
+ reviews { stars text author { firstName lastName } }
}
}
```
@@ -76,8 +76,8 @@ Nitro is the easy way: it knows the schema, autocompletes, and keeps your query
```graphql
# Write a review
mutation {
- createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", stars: 5 }) {
- review { id stars }
+ createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "cb188ef6-14e7-f85f-2f78-924c2ab26b28", stars: 5, text: "Great film. I cried at the ending." }) {
+ review { id stars text }
errors { __typename ... on Error { message } }
}
}
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/graphql/guides/secure-the-api.mdx b/src/content/docs/graphql/guides/secure-the-api.mdx
index 2593ed7..feadb9d 100644
--- a/src/content/docs/graphql/guides/secure-the-api.mdx
+++ b/src/content/docs/graphql/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:5032/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "http://localhost:8080/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -d '{"query":"{ movies { totalCount } }"}'
```
diff --git a/src/content/docs/graphql/reference/configuration.mdx b/src/content/docs/graphql/reference/configuration.mdx
index 2d06148..6c44903 100644
--- a/src/content/docs/graphql/reference/configuration.mdx
+++ b/src/content/docs/graphql/reference/configuration.mdx
@@ -55,4 +55,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:5032` and `https://localhost:7032`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
diff --git a/src/content/docs/graphql/reference/schema.mdx b/src/content/docs/graphql/reference/schema.mdx
index ff86b77..a98c98f 100644
--- a/src/content/docs/graphql/reference/schema.mdx
+++ b/src/content/docs/graphql/reference/schema.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-The schema lives in `src/Presentation/GraphQL`: queries in `Queries`, mutations in `Mutations`, object types in `Types` and the errors mutations return in `Errors`. The endpoint is `/graphql`, which also serves Nitro to a browser, and `/graphql?sdl` returns the schema as SDL. Every query and mutation, `version` included, requires authorization, which only has an effect when [authentication is on](../../guides/secure-the-api/).
+The schema lives in `src/Presentation/GraphQL`: queries in `Queries`, mutations in `Mutations`, object types in `Types` and the errors mutations return in `Errors`. The endpoint is `/graphql`, which also serves Nitro to a browser, and `/graphql?sdl` returns the schema as SDL. In a solution created with `--include-auth`, every query and mutation, `version` included, needs a token (see [Secure the API](../../guides/secure-the-api/)).
Ids are the `UUID` scalar. Field and type descriptions come from the XML comments on the code, so Nitro shows the same text as this page.
@@ -18,14 +18,14 @@ Ids are the `UUID` scalar. Field and type descriptions come from the XML comment
| `author` | `id: UUID!` | [`Author`](#author), or null when there's no author with that id |
| `authors` | `query: String`, `first: Int`, `after: String` | A [connection](#connections-and-paging) of [`AuthorSummary`](#authorsummary), ordered by last name then first name |
| `review` | `id: UUID!` | [`Review`](#review), or null when there's no review with that id |
-| `reviews` | `movieId: UUID`, `authorId: UUID`, `first: Int`, `after: String` | A [connection](#connections-and-paging) of [`Review`](#review), ordered by movie then author |
+| `reviews` | `movieId: UUID`, `authorId: UUID`, `query: String`, `minStars: Int`, `first: Int`, `after: String` | A [connection](#connections-and-paging) of [`Review`](#review), ordered by movie then author |
| `version` | none | [`VersionInfo!`](#versioninfo) |
-`query` matches anywhere in the title or the full name, ignoring case, and can be up to 100 characters. `movieId` and `authorId` filter the reviews to one movie, one author, or both.
+`query` matches anywhere in the title, the full name or, on `reviews`, the review text, ignoring case, and can be up to 100 characters. `movieId` and `authorId` filter the reviews to one movie, one author, or both, and `minStars`, from 1 to 5, keeps only reviews with at least that many stars.
```graphql
{
- movies(query: "global", first: 2) {
+ movies(query: "space", first: 2) {
totalCount
pageInfo { hasNextPage endCursor }
nodes { id title reviewCount averageStars }
@@ -37,11 +37,11 @@ Ids are the `UUID` scalar. Field and type descriptions come from the XML comment
{
"data": {
"movies": {
- "totalCount": 2,
- "pageInfo": { "hasNextPage": false, "endCursor": "b2Zmc2V0OjI=" },
+ "totalCount": 4,
+ "pageInfo": { "hasNextPage": true, "endCursor": "b2Zmc2V0OjI=" },
"nodes": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
]
}
}
@@ -60,10 +60,11 @@ The mutations follow Hot Chocolate's [mutation conventions](https://chillicream.
```graphql
mutation {
- createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", stars: 5 }) {
+ createReview(input: { authorId: "01fa8b58-05d2-abd4-e55c-2633222dd0a5", movieId: "cb188ef6-14e7-f85f-2f78-924c2ab26b28", stars: 5, text: "Great film. I cried at the ending." }) {
review {
id
stars
+ text
movie { title }
author { firstName lastName }
}
@@ -150,6 +151,7 @@ Paging only goes forwards. There's no `last` or `before`, and `hasPreviousPage`
| --- | --- | --- |
| `id` | `UUID!` | The review's id |
| `stars` | `Int!` | The rating, from 1 to 5 |
+| `text` | `String` | What the author said about the movie, or null when there's no text |
| `movie` | `ReviewedMovie` | The movie's `id` and `title` |
| `author` | `ReviewAuthor` | The author's `id`, `firstName` and `lastName` |
@@ -167,6 +169,7 @@ Paging only goes forwards. There's no `last` or `before`, and `hasPreviousPage`
| `authorId` | `UUID!` | An existing author |
| `movieId` | `UUID!` | An existing movie |
| `stars` | `Int!` | 1 to 5 |
+| `text` | `String` | Optional. Up to 1000 characters |
### UpdateReviewInput
@@ -174,6 +177,7 @@ Paging only goes forwards. There's no `last` or `before`, and `hasPreviousPage`
| --- | --- | --- |
| `id` | `UUID!` | An existing review |
| `stars` | `Int!` | 1 to 5 |
+| `text` | `String` | Optional. Up to 1000 characters. Leave it out to keep the text the review has |
### DeleteReviewInput
@@ -256,3 +260,4 @@ A query with no match doesn't error. `movie` and `author` return null, and the l
| Page size | 10 by default, 50 at most |
| Query depth | 15 |
| Search term | 100 characters |
+| Review text | 1000 characters |
diff --git a/src/content/docs/lambda/getting-started.mdx b/src/content/docs/lambda/getting-started.mdx
index f1b5ba2..8714c46 100644
--- a/src/content/docs/lambda/getting-started.mdx
+++ b/src/content/docs/lambda/getting-started.mdx
@@ -59,7 +59,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) an
```bash
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
- -d '{"query":"global","limit":2}'
+ -d '{"query":"space","limit":2}'
```
You get a page of movies back:
@@ -67,14 +67,15 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) an
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
6. Run the tests while you're here.
diff --git a/src/content/docs/lambda/guides/add-a-function.mdx b/src/content/docs/lambda/guides/add-a-function.mdx
index 360b530..aa5c5b2 100644
--- a/src/content/docs/lambda/guides/add-a-function.mdx
+++ b/src/content/docs/lambda/guides/add-a-function.mdx
@@ -107,7 +107,7 @@ This guide adds a `GetMovie` function that returns one movie with all of its rev
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetMovie_Generated::GetMovie"
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
- -d '{"id":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593"}'
+ -d '{"id":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}'
```
diff --git a/src/content/docs/lambda/guides/run-locally.mdx b/src/content/docs/lambda/guides/run-locally.mdx
index 03f4f30..5eda99b 100644
--- a/src/content/docs/lambda/guides/run-locally.mdx
+++ b/src/content/docs/lambda/guides/run-locally.mdx
@@ -48,7 +48,7 @@ docker run --rm -p 9000:8080 -e Database__SeedSampleData=true acme-reviews:local
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetReviews_Generated::GetReviews"
curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
- -d '{"movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","limit":2}'
+ -d '{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","limit":2}'
```
The sample data uses a fixed seed, so that movie id is the same on every machine. See the [function reference](../../reference/functions/) for every request and response.
diff --git a/src/content/docs/lambda/reference/functions.mdx b/src/content/docs/lambda/reference/functions.mdx
index 85c9c3a..92e9094 100644
--- a/src/content/docs/lambda/reference/functions.mdx
+++ b/src/content/docs/lambda/reference/functions.mdx
@@ -33,7 +33,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```json
-{ "query": "global", "offset": 0, "limit": 2 }
+{ "query": "space", "offset": 0, "limit": 2 }
```
### Response
@@ -50,10 +50,11 @@ A [page](#pages) of movies.
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
@@ -80,8 +81,8 @@ A [page](#pages) of authors.
```json
{
"items": [
- { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 10, "averageStars": 2.6 },
- { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 12, "averageStars": 2.7 }
+ { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 9, "averageStars": 3.3 },
+ { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 10, "averageStars": 3.2 }
],
"totalCount": 15,
"nextOffset": 2
@@ -90,7 +91,7 @@ A [page](#pages) of authors.
## GetReviews
-Lists reviews ordered by movie then author, one page at a time, optionally only those of one movie, by one author, or both.
+Lists reviews ordered by movie then author, one page at a time. Filter by movie, by author, by words in the review text and by a minimum star rating, in any combination.
### Request
@@ -98,11 +99,13 @@ Lists reviews ordered by movie then author, one page at a time, optionally only
| --- | --- | --- | --- |
| `movieId` | guid | none | Only reviews of this movie |
| `authorId` | guid | none | Only reviews by this author |
+| `query` | string | none | Up to 100 characters. Matches the review text, ignoring case |
+| `minStars` | integer | none | 1 to 5. Only reviews with at least this many stars |
| `offset` | integer | `0` | 0 or more |
| `limit` | integer | `10` | 1 to 50 |
```json
-{ "movieId": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "limit": 2 }
+{ "movieId": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "limit": 2 }
```
### Response
@@ -115,24 +118,27 @@ A [page](#pages) of reviews.
| `stars` | integer | The rating, from 1 to 5 |
| `reviewedMovie` | object | The reviewed movie's `id` and `title`. Its `reviewCount` is always 0 here, so use GetMovies for the real numbers |
| `reviewAuthor` | object | The author's `id`, `firstName` and `lastName`. Its `reviewCount` is always 0 here, so use GetAuthors for the real numbers |
+| `text` | string | What the author said about the movie. Left out when there's no text |
```json
{
"items": [
{
- "id": "969cb90c-95ae-9189-1e52-0b38d34a911b",
- "stars": 4,
- "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter", "reviewCount": 0 },
- "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 }
+ "id": "45911e6f-a366-417b-1453-f34097338ee2",
+ "stars": 5,
+ "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 0 },
+ "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 },
+ "text": "Everyone involved should be very proud."
},
{
- "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814",
- "stars": 1,
- "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter", "reviewCount": 0 },
- "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 }
+ "id": "8832846c-4100-e92a-0b66-e59dcfde2acc",
+ "stars": 3,
+ "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 0 },
+ "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 0 },
+ "text": "I laughed twice and looked at my phone once."
}
],
- "totalCount": 6,
+ "totalCount": 5,
"nextOffset": 2
}
```
@@ -172,10 +178,10 @@ The three lists are also on an [API Gateway HTTP API](https://docs.aws.amazon.co
| --- | --- | --- |
| `GET /movies` | `HttpGetMovies` | `query`, `offset`, `limit` |
| `GET /authors` | `HttpGetAuthors` | `query`, `offset`, `limit` |
-| `GET /reviews` | `HttpGetReviews` | `movieId`, `authorId`, `offset`, `limit` |
+| `GET /reviews` | `HttpGetReviews` | `movieId`, `authorId`, `query`, `minStars`, `offset`, `limit` |
```bash
-curl "${API_URL}movies?query=global&limit=2"
+curl "${API_URL}movies?query=space&limit=2"
```
| Status | When |
diff --git a/src/content/docs/mcp/explanation/decisions.mdx b/src/content/docs/mcp/explanation/decisions.mdx
index a305e61..e53a1b9 100644
--- a/src/content/docs/mcp/explanation/decisions.mdx
+++ b/src/content/docs/mcp/explanation/decisions.mdx
@@ -29,7 +29,7 @@ Every tool returns its result as text and as `structuredContent`, with an output
## Resources, prompts and completions too
-Tools are what most servers stop at. This one also exposes every movie, author and review as a resource, ships three prompts that embed the data they need, and completes titles and names as the user types them. It's there to show the shape of each, so you don't have to work out where they go.
+Tools are what most servers stop at. This one also exposes every movie, author and review as a resource, links to them from tool results, ships five prompts that embed the data they need, and completes titles, names and ids as the user types them. It's there to show the shape of each, so you don't have to work out where they go.
## Instructions for the model
diff --git a/src/content/docs/mcp/getting-started.mdx b/src/content/docs/mcp/getting-started.mdx
index 3bd0d7e..2e9e96e 100644
--- a/src/content/docs/mcp/getting-started.mdx
+++ b/src/content/docs/mcp/getting-started.mdx
@@ -58,24 +58,25 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
5. Call a tool. The server speaks Streamable HTTP at `/mcp`, and because it's stateless you can send a single JSON-RPC request with nothing before it.
```bash
- curl -X POST "http://localhost:5032/mcp" \
+ curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
- -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"global","limit":2}}}'
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"space","limit":2}}}'
```
- The answer arrives as a server-sent event. Inside it, the tool's result is there twice: once as text for models that read, once as `structuredContent` for clients that parse.
+ The answer arrives as a server-sent event. Inside it, the tool's result is there twice: once as text for models that read, once as `structuredContent` for clients that parse. A `resource_link` for each movie follows the text, so a client can open it as a resource.
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4, "reviews": null },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3, "reviews": null }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
6. Or skip curl and use the Inspector. Docker Compose starts it already pointed at the server.
diff --git a/src/content/docs/mcp/guides/add-a-tool.mdx b/src/content/docs/mcp/guides/add-a-tool.mdx
index 054172e..0ca6da0 100644
--- a/src/content/docs/mcp/guides/add-a-tool.mdx
+++ b/src/content/docs/mcp/guides/add-a-tool.mdx
@@ -89,7 +89,7 @@ This guide adds `authors_get`, which returns one author with every review they'v
```
```bash
- curl -X POST "http://localhost:5032/mcp" \
+ curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"authors_get","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5"}}}'
```
@@ -98,16 +98,16 @@ This guide adds `authors_get`, which returns one author with every review they'v
{
"id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5",
"name": "Jana Auer",
- "reviewCount": 10,
- "averageStars": 2.6,
+ "reviewCount": 9,
+ "averageStars": 3.3,
"reviews": [
- { "id": "0c13d0af-31ab-1705-228a-619b147ed110", "stars": 2, "movieId": "aea49d3b-0e68-0098-d9c3-70c93ea9e900", "movieTitle": "Persevering non-volatile middleware", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer" },
- { "id": "19bbc1d8-960a-df45-df54-46ad502c3013", "stars": 2, "movieId": "ecff0774-e2f7-f169-49c9-a6820ae88b30", "movieTitle": "Customizable needs-based open architecture", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer" }
+ { "id": "03ca222d-2de3-5eef-ec0b-d3915b23c4de", "stars": 2, "movieId": "c68c95ac-b7fa-4134-761c-0014588bfa01", "movieTitle": "The Courtroom Cockatoo", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer", "text": "Twenty minutes too long, in a film that needed to be an hour shorter." },
+ { "id": "0ac92e89-50e3-543b-49d7-13ee4064b32e", "stars": 4, "movieId": "c510b73e-4967-949a-7098-8263bb7b8d39", "movieTitle": "Midnight at the Laundromat", "authorId": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "authorName": "Jana Auer", "text": "It takes a while to warm up, then it really goes." }
]
}
```
- Jana averages 2.6 stars across ten reviews. Jana is not easily impressed.
+ Jana averages 3.3 stars across nine reviews, and sat through *Apprentice to a Retired Dragon* out of spite.
diff --git a/src/content/docs/mcp/guides/connect-a-client.mdx b/src/content/docs/mcp/guides/connect-a-client.mdx
index e876623..9cfdfef 100644
--- a/src/content/docs/mcp/guides/connect-a-client.mdx
+++ b/src/content/docs/mcp/guides/connect-a-client.mdx
@@ -7,12 +7,14 @@ sidebar:
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
-The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with `dotnet run --project ./src/Presentation` and give the client `http://localhost:5032/mcp`. With Docker Compose the address is `http://localhost:8080/mcp`.
+The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with `dotnet run --project ./src/Presentation` or Docker Compose, and give the client `http://localhost:8080/mcp`. Both listen there.
+
+No client to hand? Docker Compose can start one for you: see [Chat with it](../run-locally/#chat-with-it).
```bash
- claude mcp add --transport http acme-movies http://localhost:5032/mcp
+ claude mcp add --transport http acme-movies http://localhost:8080/mcp
```
Then ask it something like "which movie has the best reviews?" and watch it call `movies_search` and `movies_get` on its own.
@@ -26,7 +28,7 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
```json
{
"servers": {
- "acme-movies": { "type": "http", "url": "http://localhost:5032/mcp" }
+ "acme-movies": { "type": "http", "url": "http://localhost:8080/mcp" }
}
}
```
@@ -37,7 +39,7 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
```json
{
"mcpServers": {
- "acme-movies": { "url": "http://localhost:5032/mcp" }
+ "acme-movies": { "url": "http://localhost:8080/mcp" }
}
}
```
@@ -49,9 +51,9 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
using ModelContextProtocol.Client;
await using var client = await McpClient.CreateAsync(
- new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:5032/mcp") }));
+ new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:8080/mcp") }));
- var result = await client.CallToolAsync("movies_search", new Dictionary { ["query"] = "global" });
+ var result = await client.CallToolAsync("movies_search", new Dictionary { ["query"] = "space" });
```
@@ -60,7 +62,7 @@ The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specificatio
On `initialize` the server introduces itself with a name, title, version, description, icon and website, and a block of instructions for the model:
-> This server holds a catalogue of movies, the authors who review them, and their reviews. A review links one author to one movie with a star rating from 1 (worst) to 5 (best). Find things by name first: movies_search and authors_search return ids, review counts and average stars, and reviews_list filters reviews by movie or author. Every search returns one page at a time, so pass nextOffset back as the offset to continue rather than raising the limit. reviews_write creates a review, or changes the stars of one when you pass its reviewId. A review's movie and author can't change. reviews_delete can't be undone, so confirm with the user before calling it. Each movie, author and review can also be read as a resource at movies://\{id\}, authors://\{id\} or reviews://\{id\}, and server://version gives this server's version.
+> This server holds a catalogue of movies, the authors who review them, and their reviews. A review links one author to one movie with a star rating from 1 (worst) to 5 (best). Find things by name first: movies_search and authors_search return ids, review counts and average stars, and reviews_list filters reviews by movie or author. Every search returns one page at a time, so pass nextOffset back as the offset to continue rather than raising the limit. reviews_write creates a review, or changes the stars of one when you pass its reviewId. A review's movie and author can't change. reviews_delete can't be undone, so confirm with the user before calling it. Each movie, author and review can also be read as a resource at movies://\{id\}, authors://\{id\} or reviews://\{id\}. movies://top-rated lists the ten best rated movies, and server://version gives this server's version.
Those instructions are in `WebApplicationBuilderExtensions.cs`, and they're worth keeping up to date when you add a tool. A model that knows the shape of the data makes fewer calls and better ones.
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index 6c9aef2..9eab124 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:5032](http://localhost:5032) and `https://localhost:7032`. The server is at `/mcp`.
+ `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose. The server is at `/mcp`.
```bash
@@ -45,6 +45,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/ready` | Answers 200 when the database can be reached |
| `/.well-known/oauth-protected-resource/mcp` | Protected resource metadata, only when [authentication is on](../secure-the-server/) |
| [http://localhost:6274](http://localhost:6274) | The MCP Inspector, when running with Docker Compose |
+| [http://localhost:3000](http://localhost:3000) | Open WebUI, when running with Docker Compose and the `demo` profile |
| [http://localhost:18888](http://localhost:18888) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
@@ -53,11 +54,21 @@ The health endpoints answer without a token even when authentication is on, so a
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the official test client, and Docker Compose runs it already pointed at `http://api:8080/mcp`. Open it, press **Connect**, and:
-- **Tools** lists every tool with its description, annotations and input schema, and gives you a form to call it.
-- **Resources** lets you read `server://version` or any `movies://`, `authors://` or `reviews://` URI.
-- **Prompts** renders each prompt with its arguments, and the title and name arguments autocomplete from the database.
+- **Tools** lists every tool with its description, annotations and input schema, and gives you a form to call it. A result lists its movies, authors and reviews under **Resource Links**, and expanding one reads it.
+- **Resources** lets you read `movies://top-rated`, `server://version` or any `movies://`, `authors://` or `reviews://` URI, and the ids autocomplete as you type an id or a title.
+- **Prompts** renders each prompt with its arguments, and the title, name and id arguments autocomplete from the database.
-Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `http://localhost:5032/mcp`.
+Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `http://localhost:8080/mcp`.
+
+## Chat with it
+
+Want to ask for things in plain English? The optional `demo` profile adds [Open WebUI](https://github.com/open-webui/open-webui), already connected to the server.
+
+```bash
+docker compose --profile demo up -d --build
+```
+
+Open [http://localhost:3000](http://localhost:3000), dismiss the release notes and ask it to list the movies. It uses Kilo's free gateway as the model, so there's no account or key, but it only works online, and your prompts and tool results go to Kilo, a third party. Keep it to sample data. Solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. Stop it with `docker compose --profile demo down`.
## See the telemetry
@@ -76,17 +87,17 @@ The server is stateless, so a single `tools/call` works without an `initialize`
```bash
# One movie, with every review
-curl -X POST "http://localhost:5032/mcp" \
+curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
- -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593"}}}'
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}}}'
# Write a review
-curl -X POST "http://localhost:5032/mcp" \
+curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}}}'
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}}}'
# Read a resource
-curl -X POST "http://localhost:5032/mcp" \
+curl -X POST "http://localhost:8080/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
```
@@ -125,5 +136,5 @@ docker build . --target run --tag acme-movies:latest \
The `server://version` resource and the `serverInfo` in the initialize response then report `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/mcp/guides/secure-the-server.mdx b/src/content/docs/mcp/guides/secure-the-server.mdx
index ab801d9..dd49da8 100644
--- a/src/content/docs/mcp/guides/secure-the-server.mdx
+++ b/src/content/docs/mcp/guides/secure-the-server.mdx
@@ -42,7 +42,7 @@ Out of the box the server runs wide open, and there's no authentication code in
3. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:5032/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "http://localhost:8080/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
diff --git a/src/content/docs/mcp/index.mdx b/src/content/docs/mcp/index.mdx
index ebd491d..197c6b6 100644
--- a/src/content/docs/mcp/index.mdx
+++ b/src/content/docs/mcp/index.mdx
@@ -27,7 +27,7 @@ import Family from '../../../components/docs/Family.astro';
Six tools named for the jobs an agent actually does, like `movies_search` and `reviews_write`, with read-only, destructive and idempotent hints.
- Resources for every movie, author and review, plus prompts with completions that suggest titles and names as you type.
+ Resources for every movie, author and review, linked from tool results, plus prompts with completions that suggest titles, names and ids as you type.
Validation lives in the Application layer with FluentValidation, and tool errors tell the agent how to recover instead of just giving up.
diff --git a/src/content/docs/mcp/reference/configuration.mdx b/src/content/docs/mcp/reference/configuration.mdx
index 0e316bc..71946eb 100644
--- a/src/content/docs/mcp/reference/configuration.mdx
+++ b/src/content/docs/mcp/reference/configuration.mdx
@@ -57,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:5032` and `https://localhost:7032`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
diff --git a/src/content/docs/mcp/reference/resources-and-prompts.mdx b/src/content/docs/mcp/reference/resources-and-prompts.mdx
index bf93fe1..f0b7b02 100644
--- a/src/content/docs/mcp/reference/resources-and-prompts.mdx
+++ b/src/content/docs/mcp/reference/resources-and-prompts.mdx
@@ -5,18 +5,19 @@ sidebar:
order: 2
---
-Resources live in `src/Presentation/Resources`, prompts in `src/Presentation/Prompts`, and the completion handler in `src/Presentation/Completions`. All of them need a token when [authentication is on](../../guides/secure-the-server/).
+Resources live in `src/Presentation/Resources`, prompts in `src/Presentation/Prompts`, and the completion handler in `src/Presentation/Completions`. In a solution created with `--include-auth`, all of them need a token. Every resource and prompt has an icon, a movie, an author, a review or the server, from the [brand repository](https://github.com/LeanMeanCleanApps/brand).
## Resources
-Every resource returns `application/json`. The movie, author and review resources are templates, so they're listed under `resources/templates/list`; the version resource is a plain resource under `resources/list`.
+Every resource returns `application/json`. The movie, author and review resources are templates, so they're listed under `resources/templates/list`; the top rated and version resources are plain resources under `resources/list`.
| URI | Name | Returns |
| --- | --- | --- |
+| `movies://top-rated` | `top_rated_movies` | A [page](../tools/#pages) of the ten best rated [movies](../tools/#movie), highest average stars first |
| `movies://{movieId}` | `movie` | A [movie](../tools/#movie) with every review |
| `authors://{authorId}` | `author` | An [author](../tools/#author) with every review |
| `reviews://{reviewId}` | `review` | A [review](../tools/#review) |
-| `server://version` | `version` | The version stamped into the build |
+| `server://version` | `server_version` | The version stamped into the build |
```json
{ "fileVersion": "1.1.0.0", "informationalVersion": "1.1.0.0+c7e22dee4ffd508b82caca702ebb713d0a2ba8bc" }
@@ -29,15 +30,25 @@ An unknown id fails with the JSON-RPC `-32002` resource not found error, and an
| Prompt | Arguments | What it asks the model to do |
| --- | --- | --- |
| `summarise_movie_reception` | `movieTitle` | Summarise a movie's average stars, how its ratings spread, and who liked it most and least |
+| `summarise_movie_reviews` | `movieId` | Summarise what reviewers said about a movie: the themes they agree on, where they disagree, and whether their words match their stars |
| `compare_movies` | `firstMovieTitle`, `secondMovieTitle` | Compare how reviewers rated two movies and say which they preferred |
-| `review_movie` | `movieTitle`, `authorName` | Find the movie and the author, ask for a star rating, and save the review with `reviews_write` |
+| `summarise_author_reviews` | `authorName` | Summarise an author's reviewing habits: how many reviews, how generous they are, and the movies they rated highest and lowest |
+| `review_movie` | `movieTitle`, `authorName` | Find the movie and the author, ask for a star rating and what they thought, and save the review with `reviews_write` |
-The first two look the movie up by title. When they find exactly one match, the returned messages embed the movie as a `movies://` resource, reviews and all, so the model has the data without another call. When they don't, the messages tell the model which tools to use to find it.
+`summarise_movie_reception` and `compare_movies` look the movie up by title, and `summarise_author_reviews` looks the author up by name. When they find it, by an exact match or as the only match, the returned messages embed it as a `movies://` or `authors://` resource, reviews and all, so the model has the data without another call. When they don't, the messages tell the model which tools to use to find it. `summarise_movie_reviews` takes the movie's id, so it always embeds the movie, or fails with the JSON-RPC `-32002` error when there's no movie with that id.
## Completions
-The `movieTitle`, `firstMovieTitle` and `secondMovieTitle` arguments complete from movie titles, and `authorName` completes from author names, up to 50 at a time with the total count. A value over 100 characters, or an argument the handler doesn't know, completes to nothing.
+The `movieTitle`, `firstMovieTitle` and `secondMovieTitle` arguments complete from movie titles, and `authorName` completes from author names, up to 50 at a time with the total count.
```json
-{ "completion": { "values": ["Assimilated global infrastructure", "Automated global alliance"], "total": 2, "hasMore": false } }
+{ "completion": { "values": ["Deep Space Gardeners", "Postcards from Outer Space", "Space Station Bake Sale", "The Last Space Lighthouse"], "total": 4, "hasMore": false } }
```
+
+Ids complete too: the `movieId` argument of `summarise_movie_reviews`, and the ids in the `movies://{movieId}`, `authors://{authorId}` and `reviews://{reviewId}` templates. Type the start of an id, or part of the movie's title or the author's name, and you get the matching ids. A review is found by its movie's title.
+
+```json
+{ "completion": { "values": ["cb188ef6-14e7-f85f-2f78-924c2ab26b28", "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "ec850910-9692-cf5d-8ce4-938b304e9228", "ec68dc8c-2c08-5139-b68f-6c4be5061bdb"], "total": 4, "hasMore": false } }
+```
+
+A value over 100 characters, or an argument the handler doesn't know, completes to nothing.
diff --git a/src/content/docs/mcp/reference/tools.mdx b/src/content/docs/mcp/reference/tools.mdx
index e5b4966..630a472 100644
--- a/src/content/docs/mcp/reference/tools.mdx
+++ b/src/content/docs/mcp/reference/tools.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-Every tool lives in `src/Presentation/Tools`, one static class per feature. Arguments are JSON, named in camelCase. Every tool returns its result twice: as a text content block holding JSON, for models that read, and as `structuredContent`, for clients that parse, with an output schema in the tool's listing. When [authentication is on](../../guides/secure-the-server/), every tool needs a token.
+Every tool lives in `src/Presentation/Tools`, one static class per feature. Arguments are JSON, named in camelCase. Every tool returns its result twice: as a text content block holding JSON, for models that read, and as `structuredContent`, for clients that parse, with an output schema in the tool's listing. After the text comes a `resource_link` block for each movie, author or review in the result, pointing at its `movies://`, `authors://` or `reviews://` [resource](../resources-and-prompts/), so a client can open it. In a solution created with `--include-auth`, every tool needs a token.
| Tool | Title | Read-only | Destructive | Idempotent |
| --- | --- | --- | --- | --- |
@@ -16,7 +16,7 @@ Every tool lives in `src/Presentation/Tools`, one static class per feature. Argu
| [`reviews_write`](#reviews_write) | Write review | | yes | |
| [`reviews_delete`](#reviews_delete) | Delete review | | yes | yes |
-Every tool has `openWorldHint` false: they only ever touch this server's own database.
+Every tool has `openWorldHint` false: they only ever touch this server's own database. Every tool also has an icon, a movie, an author or a review, from the [brand repository](https://github.com/LeanMeanCleanApps/brand).
## movies_search
@@ -34,10 +34,11 @@ Returns a [page](#pages) of [movies](#movie).
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4, "reviews": null },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3, "reviews": null }
],
- "totalCount": 2
+ "totalCount": 4,
+ "nextOffset": 2
}
```
@@ -80,7 +81,7 @@ Returns a [page](#pages) of [reviews](#review).
## reviews_write
-Creates a review of a movie by an author, or changes the stars of an existing review when `reviewId` is passed. A review's movie and author can't change.
+Creates a review of a movie by an author, with optional text, or changes the stars and text of an existing review when `reviewId` is passed. A review's movie and author can't change.
| Argument | Type | Rules |
| --- | --- | --- |
@@ -88,6 +89,7 @@ Creates a review of a movie by an author, or changes the stars of an existing re
| `movieId` | guid | Needed to create a review. An existing movie |
| `authorId` | guid | Needed to create a review. An existing author |
| `reviewId` | guid | Leave empty to create. An existing review to change |
+| `text` | string | Optional. Up to 1000 characters. Leave it empty when changing a review to keep its text |
Returns the saved [review](#review).
@@ -103,6 +105,8 @@ Returns `{ "reviewId": "...", "deleted": true }`.
## Results
+Properties with no value are written as `null`, not left out, so every result matches its output schema. The schema describes them with `anyOf` and a `null` type, which clients that only understand one type per property accept too.
+
### Pages
Every search and list tool returns one page.
@@ -111,7 +115,7 @@ Every search and list tool returns one page.
| --- | --- | --- |
| `items` | array | The results on this page |
| `totalCount` | integer | The number of results across every page |
-| `nextOffset` | integer | Pass it as `offset` to get the next page. Left out on the last page |
+| `nextOffset` | integer | Pass it as `offset` to get the next page. Null on the last page |
### Movie
@@ -120,8 +124,8 @@ Every search and list tool returns one page.
| `id` | guid | The movie's id |
| `title` | string | The movie's title |
| `reviewCount` | integer | How many reviews the movie has |
-| `averageStars` | number | The average rating to one decimal place. Left out when there are no reviews |
-| `reviews` | array of [review](#review) | Every review of the movie. Only when asked for |
+| `averageStars` | number | The average rating to one decimal place. Null when there are no reviews |
+| `reviews` | array of [review](#review) | Every review of the movie. Null unless asked for |
### Author
@@ -130,8 +134,8 @@ Every search and list tool returns one page.
| `id` | guid | The author's id |
| `name` | string | The author's full name |
| `reviewCount` | integer | How many reviews the author has written |
-| `averageStars` | number | The average rating they give. Left out when there are no reviews |
-| `reviews` | array of [review](#review) | Every review by the author. Only when asked for |
+| `averageStars` | number | The average rating they give. Null when there are no reviews |
+| `reviews` | array of [review](#review) | Every review by the author. Null unless asked for |
### Review
@@ -143,6 +147,7 @@ Every search and list tool returns one page.
| `movieTitle` | string | The movie's title |
| `authorId` | guid | The author's id |
| `authorName` | string | The author's full name |
+| `text` | string | What the author said about the movie, or null when there's no text |
## Errors
diff --git a/src/content/docs/minimalapi/getting-started.mdx b/src/content/docs/minimalapi/getting-started.mdx
index 6484523..8de91fe 100644
--- a/src/content/docs/minimalapi/getting-started.mdx
+++ b/src/content/docs/minimalapi/getting-started.mdx
@@ -55,12 +55,12 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open the Scalar API reference at [http://localhost:5032/scalar](http://localhost:5032/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+5. Open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "http://localhost:5032/api/movies?query=global&limit=2"
+ curl "http://localhost:8080/api/movies?query=space&limit=2"
```
You get a page of movies back:
@@ -68,16 +68,16 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
7. Run the tests while you're here.
diff --git a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
index 9dcebb8..3dc998e 100644
--- a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
@@ -101,22 +101,22 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:5032/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593/reviews?limit=2"
+ curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
],
"offset": 0,
"limit": 2,
- "totalCount": 6
+ "totalCount": 5
}
```
- Caroline gave the same movie a 4 and a 1. Sample data has no continuity editor.
+ Caroline gave the same movie a 5 and a 3. Sample data has no continuity editor.
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 90bf1f3..2707e7e 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:5032](http://localhost:5032) and `https://localhost:7032`.
+ `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
```bash
@@ -66,14 +66,14 @@ The sample data uses a fixed seed, so these ids are the same on every machine. S
```bash
# Search movies, two at a time
-curl "http://localhost:5032/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:5032/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593"
+curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "http://localhost:5032/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
## Run the tests
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/minimalapi/guides/secure-the-api.mdx b/src/content/docs/minimalapi/guides/secure-the-api.mdx
index eeb0121..ce5fac6 100644
--- a/src/content/docs/minimalapi/guides/secure-the-api.mdx
+++ b/src/content/docs/minimalapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "http://localhost:5032/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "http://localhost:8080/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/minimalapi/reference/configuration.mdx b/src/content/docs/minimalapi/reference/configuration.mdx
index 1ce02e7..863ca90 100644
--- a/src/content/docs/minimalapi/reference/configuration.mdx
+++ b/src/content/docs/minimalapi/reference/configuration.mdx
@@ -55,4 +55,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:5032` and `https://localhost:7032`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
diff --git a/src/content/docs/minimalapi/reference/endpoints.mdx b/src/content/docs/minimalapi/reference/endpoints.mdx
index 231728a..325013c 100644
--- a/src/content/docs/minimalapi/reference/endpoints.mdx
+++ b/src/content/docs/minimalapi/reference/endpoints.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. Every endpoint requires authorization, which only has an effect when [authentication is on](../../guides/secure-the-api/).
+Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
@@ -35,18 +35,18 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:5032/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
```
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
@@ -63,8 +63,8 @@ Lists authors ordered by last name then first name, one page at a time, optional
```json
{
"items": [
- { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 10, "averageStars": 2.6 },
- { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 12, "averageStars": 2.7 }
+ { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 9, "averageStars": 3.3 },
+ { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 10, "averageStars": 3.2 }
],
"offset": 0,
"limit": 2,
@@ -80,24 +80,42 @@ Returns one [author](#author) with every review they have written, or `404` when
### GET /api/reviews
-Lists reviews ordered by movie then author, one page at a time, optionally only those of one movie, by one author, or both.
+Lists reviews ordered by movie then author, one page at a time. Filter by movie, by author, by words in the review text and by a minimum star rating, in any combination.
| Query parameter | Type | Default | Rules |
| --- | --- | --- | --- |
| `movieId` | guid | none | Only reviews of this movie |
| `authorId` | guid | none | Only reviews by this author |
+| `query` | string | none | Up to 100 characters. Matches the review text, ignoring case |
+| `minStars` | integer | none | 1 to 5. Only reviews with at least this many stars |
| `offset` | integer | `0` | 0 or more |
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:5032/api/reviews?movieId=e159975e-4e6a-1ffd-713e-1eb9d8d4a593&limit=2"
+curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
+ ],
+ "offset": 0,
+ "limit": 2,
+ "totalCount": 5
+}
+```
+
+```bash
+curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+```
+
+```json
+{
+ "items": [
+ { "id": "85196da9-b404-4905-a1f8-9e0dc88f67e4", "stars": 5, "reviewedMovie": { "id": "33b07882-f520-65ee-5997-b1024edd80b5", "title": "A Quiet Murder in Wagga Wagga" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, sad and completely gripping." },
+ { "id": "625d83d4-587f-f872-f5b5-7f923a37856f", "stars": 4, "reviewedMovie": { "id": "ac6e5917-e549-db65-a0b7-aff1fc817570", "title": "Dragon of the Salt Flats" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, warm and better than it needed to be." }
],
"offset": 0,
"limit": 2,
@@ -118,21 +136,23 @@ Creates a review. The author and the movie must already exist.
| `authorId` | guid | Required. An existing author |
| `movieId` | guid | Required. An existing movie |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "http://localhost:5032/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
Answers `201 Created` with the new [review](#review) in the body and its URL in the `Location` header. An unknown author or movie answers `404`.
### PUT /api/reviews/\{id\}
-Changes the star rating of a review. The author and the movie of a review can't change.
+Changes the star rating of a review, and its text when there's new text. The author and the movie of a review can't change.
| Body property | Type | Rules |
| --- | --- | --- |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters. Leave it out to keep the text the review has |
Answers `204 No Content`, or `404` when there's no review with that id.
@@ -144,7 +164,7 @@ Permanently removes a review. Answers `204 No Content`, or `404` when there's no
### GET /api/version
-Returns the [version](#version) stamped into the build. It needs a token when authentication is on, like everything else; the version is also on every span, metric and log as `service.version`.
+Returns the [version](#version) stamped into the build. With `--include-auth` it needs a token, like everything else; the version is also on every span, metric and log as `service.version`.
```json
{ "fileVersion": "1.1.0.0", "informationalVersion": "1.1.0.0+044d2922e197f24d9a3f311f87a7e323add1a530" }
@@ -209,6 +229,7 @@ To read everything, start at `offset` 0 and add `limit` to it until `offset` rea
| `stars` | integer | The rating, from 1 to 5 |
| `reviewedMovie` | object | The movie's `id` and `title` |
| `reviewAuthor` | object | The author's `id`, `firstName` and `lastName` |
+| `text` | string | What the author said about the movie. Left out when there's no text |
### Version
diff --git a/src/content/docs/webapi/getting-started.mdx b/src/content/docs/webapi/getting-started.mdx
index 59522f7..52aacd0 100644
--- a/src/content/docs/webapi/getting-started.mdx
+++ b/src/content/docs/webapi/getting-started.mdx
@@ -60,7 +60,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or call it from the terminal.
```bash
- curl "http://localhost:8080/api/movies?query=global&limit=2"
+ curl "http://localhost:8080/api/movies?query=space&limit=2"
```
You get a page of movies back:
@@ -68,16 +68,16 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
- Yes, the movies are named by a corporate buzzword generator. *Assimilated global infrastructure* only got one star. Critics can be harsh.
+ The sample movies are all made up. *Deep Space Gardeners* averages 3.4 stars from five reviews, which seems fair for a film about weeding in zero gravity.
7. Run the tests while you're here.
diff --git a/src/content/docs/webapi/guides/add-an-endpoint.mdx b/src/content/docs/webapi/guides/add-an-endpoint.mdx
index e19f29d..957173b 100644
--- a/src/content/docs/webapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/webapi/guides/add-an-endpoint.mdx
@@ -95,22 +95,22 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593/reviews?limit=2"
+ curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
],
"offset": 0,
"limit": 2,
- "totalCount": 6
+ "totalCount": 5
}
```
- Caroline gave the same movie a 4 and a 1. Sample data has no continuity editor.
+ Caroline gave the same movie a 5 and a 3. Sample data has no continuity editor.
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index 3d8c023..abb95a4 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -66,14 +66,14 @@ The sample data uses a fixed seed, so these ids are the same on every machine. S
```bash
# Search movies, two at a time
-curl "http://localhost:8080/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:8080/api/movies/e159975e-4e6a-1ffd-713e-1eb9d8d4a593"
+curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
## Run the tests
diff --git a/src/content/docs/webapi/reference/endpoints.mdx b/src/content/docs/webapi/reference/endpoints.mdx
index 86dbcd1..87d3b5f 100644
--- a/src/content/docs/webapi/reference/endpoints.mdx
+++ b/src/content/docs/webapi/reference/endpoints.mdx
@@ -5,7 +5,7 @@ sidebar:
order: 1
---
-Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. Every endpoint requires authorization, which only has an effect when [authentication is on](../../guides/secure-the-api/).
+Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
@@ -35,18 +35,18 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/movies?query=global&limit=2"
+curl "http://localhost:8080/api/movies?query=space&limit=2"
```
```json
{
"items": [
- { "id": "30fca414-247a-779e-105a-d81aa2506f58", "title": "Assimilated global infrastructure", "reviewCount": 1, "averageStars": 1 },
- { "id": "c27d6600-79a6-9346-2595-a52b7f07dc3e", "title": "Automated global alliance", "reviewCount": 3, "averageStars": 3.3 }
+ { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners", "reviewCount": 5, "averageStars": 3.4 },
+ { "id": "dfefa450-5454-6a4d-a4aa-95a9b047c17b", "title": "Postcards from Outer Space", "reviewCount": 1, "averageStars": 3 }
],
"offset": 0,
"limit": 2,
- "totalCount": 2
+ "totalCount": 4
}
```
@@ -63,8 +63,8 @@ Lists authors ordered by last name then first name, one page at a time, optional
```json
{
"items": [
- { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 10, "averageStars": 2.6 },
- { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 12, "averageStars": 2.7 }
+ { "id": "01fa8b58-05d2-abd4-e55c-2633222dd0a5", "firstName": "Jana", "lastName": "Auer", "reviewCount": 9, "averageStars": 3.3 },
+ { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins", "reviewCount": 10, "averageStars": 3.2 }
],
"offset": 0,
"limit": 2,
@@ -80,24 +80,42 @@ Returns one [author](#author) with every review they have written, or `404` when
### GET /api/reviews
-Lists reviews ordered by movie then author, one page at a time, optionally only those of one movie, by one author, or both.
+Lists reviews ordered by movie then author, one page at a time. Filter by movie, by author, by words in the review text and by a minimum star rating, in any combination.
| Query parameter | Type | Default | Rules |
| --- | --- | --- | --- |
| `movieId` | guid | none | Only reviews of this movie |
| `authorId` | guid | none | Only reviews by this author |
+| `query` | string | none | Up to 100 characters. Matches the review text, ignoring case |
+| `minStars` | integer | none | 1 to 5. Only reviews with at least this many stars |
| `offset` | integer | `0` | 0 or more |
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/reviews?movieId=e159975e-4e6a-1ffd-713e-1eb9d8d4a593&limit=2"
+curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
{
"items": [
- { "id": "969cb90c-95ae-9189-1e52-0b38d34a911b", "stars": 4, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } },
- { "id": "a5ebd747-0408-0a96-1af1-eb5cb93bc814", "stars": 1, "reviewedMovie": { "id": "e159975e-4e6a-1ffd-713e-1eb9d8d4a593", "title": "Assimilated disintermediate adapter" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" } }
+ { "id": "45911e6f-a366-417b-1453-f34097338ee2", "stars": 5, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "Everyone involved should be very proud." },
+ { "id": "8832846c-4100-e92a-0b66-e59dcfde2acc", "stars": 3, "reviewedMovie": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "title": "Deep Space Gardeners" }, "reviewAuthor": { "id": "1cc2ad51-61c5-63ee-dd6e-15308cfe6e51", "firstName": "Caroline", "lastName": "Bins" }, "text": "I laughed twice and looked at my phone once." }
+ ],
+ "offset": 0,
+ "limit": 2,
+ "totalCount": 5
+}
+```
+
+```bash
+curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+```
+
+```json
+{
+ "items": [
+ { "id": "85196da9-b404-4905-a1f8-9e0dc88f67e4", "stars": 5, "reviewedMovie": { "id": "33b07882-f520-65ee-5997-b1024edd80b5", "title": "A Quiet Murder in Wagga Wagga" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, sad and completely gripping." },
+ { "id": "625d83d4-587f-f872-f5b5-7f923a37856f", "stars": 4, "reviewedMovie": { "id": "ac6e5917-e549-db65-a0b7-aff1fc817570", "title": "Dragon of the Salt Flats" }, "reviewAuthor": { "id": "cb188ef6-14e7-f85f-2f78-924c2ab26b28", "firstName": "Martha", "lastName": "Nicolas" }, "text": "Funny, warm and better than it needed to be." }
],
"offset": 0,
"limit": 2,
@@ -118,21 +136,23 @@ Creates a review. The author and the movie must already exist.
| `authorId` | guid | Required. An existing author |
| `movieId` | guid | Required. An existing movie |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters |
```bash
curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
- -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593","stars":5}'
+ -d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
Answers `201 Created` with the new [review](#review) in the body and its URL in the `Location` header. An unknown author or movie answers `404`.
### PUT /api/reviews/\{id\}
-Changes the star rating of a review. The author and the movie of a review can't change.
+Changes the star rating of a review, and its text when there's new text. The author and the movie of a review can't change.
| Body property | Type | Rules |
| --- | --- | --- |
| `stars` | integer | 1 to 5 |
+| `text` | string | Optional. Up to 1000 characters. Leave it out to keep the text the review has |
Answers `204 No Content`, or `404` when there's no review with that id.
@@ -144,7 +164,7 @@ Permanently removes a review. Answers `204 No Content`, or `404` when there's no
### GET /api/version
-Returns the [version](#version) stamped into the build. It needs a token when authentication is on, like everything else; the version is also on every span, metric and log as `service.version`.
+Returns the [version](#version) stamped into the build. With `--include-auth` it needs a token, like everything else; the version is also on every span, metric and log as `service.version`.
```json
{ "fileVersion": "1.1.0.0", "informationalVersion": "1.1.0.0+044d2922e197f24d9a3f311f87a7e323add1a530" }
@@ -209,6 +229,7 @@ To read everything, start at `offset` 0 and add `limit` to it until `offset` rea
| `stars` | integer | The rating, from 1 to 5 |
| `reviewedMovie` | object | The movie's `id` and `title` |
| `reviewAuthor` | object | The author's `id`, `firstName` and `lastName` |
+| `text` | string | What the author said about the movie. Left out when there's no text |
### Version
From c6af68df004e3373910a9d9efc66a3fbd0b2a46d Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:03:40 +0000
Subject: [PATCH 2/6] Use the HTTPS address in the dotnet run examples
dotnet run redirects http://localhost:8080 to https://localhost:8081, so a
curl against the HTTP address got an empty 307. The examples use the HTTPS
address and say to trust the development certificate once. Docker Compose
only listens on HTTP, so its examples keep http://localhost:8080.
---
src/content/docs/graphql/getting-started.mdx | 6 ++++--
src/content/docs/graphql/guides/add-a-field.mdx | 2 +-
src/content/docs/graphql/guides/run-locally.mdx | 4 ++--
src/content/docs/graphql/guides/secure-the-api.mdx | 2 +-
src/content/docs/mcp/getting-started.mdx | 4 +++-
src/content/docs/mcp/guides/add-a-tool.mdx | 2 +-
src/content/docs/mcp/guides/connect-a-client.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 12 ++++++------
src/content/docs/mcp/guides/secure-the-server.mdx | 2 +-
src/content/docs/minimalapi/getting-started.mdx | 6 ++++--
.../docs/minimalapi/guides/add-an-endpoint.mdx | 2 +-
src/content/docs/minimalapi/guides/run-locally.mdx | 10 +++++-----
.../docs/minimalapi/guides/secure-the-api.mdx | 2 +-
src/content/docs/minimalapi/reference/endpoints.mdx | 10 +++++-----
src/content/docs/webapi/getting-started.mdx | 6 ++++--
src/content/docs/webapi/guides/add-an-endpoint.mdx | 2 +-
src/content/docs/webapi/guides/run-locally.mdx | 10 +++++-----
src/content/docs/webapi/guides/secure-the-api.mdx | 2 +-
src/content/docs/webapi/reference/endpoints.mdx | 10 +++++-----
19 files changed, 52 insertions(+), 44 deletions(-)
diff --git a/src/content/docs/graphql/getting-started.mdx b/src/content/docs/graphql/getting-started.mdx
index 993ed04..d5589dc 100644
--- a/src/content/docs/graphql/getting-started.mdx
+++ b/src/content/docs/graphql/getting-started.mdx
@@ -55,7 +55,9 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open Nitro, the GraphQL IDE, at [http://localhost:8080/graphql](http://localhost:8080/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
+5. Open Nitro, the GraphQL IDE, at [https://localhost:8081/graphql](https://localhost:8081/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
```graphql
{
@@ -70,7 +72,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or ask from the terminal.
```bash
- curl -X POST "http://localhost:8080/graphql" -H "Content-Type: application/json" \
+ curl -X POST "https://localhost:8081/graphql" -H "Content-Type: application/json" \
-d '{"query":"{ movies(query: \"space\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
```
diff --git a/src/content/docs/graphql/guides/add-a-field.mdx b/src/content/docs/graphql/guides/add-a-field.mdx
index d93bb46..3910ff9 100644
--- a/src/content/docs/graphql/guides/add-a-field.mdx
+++ b/src/content/docs/graphql/guides/add-a-field.mdx
@@ -60,7 +60,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```bash
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
+ curl "https://localhost:8081/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
```
The diff is the one field, described:
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index b0f362b..6d44587 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/graphql/guides/secure-the-api.mdx b/src/content/docs/graphql/guides/secure-the-api.mdx
index feadb9d..8561b56 100644
--- a/src/content/docs/graphql/guides/secure-the-api.mdx
+++ b/src/content/docs/graphql/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:8080/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:8081/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -d '{"query":"{ movies { totalCount } }"}'
```
diff --git a/src/content/docs/mcp/getting-started.mdx b/src/content/docs/mcp/getting-started.mdx
index 2e9e96e..b58d294 100644
--- a/src/content/docs/mcp/getting-started.mdx
+++ b/src/content/docs/mcp/getting-started.mdx
@@ -55,10 +55,12 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
5. Call a tool. The server speaks Streamable HTTP at `/mcp`, and because it's stateless you can send a single JSON-RPC request with nothing before it.
```bash
- curl -X POST "http://localhost:8080/mcp" \
+ curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"space","limit":2}}}'
```
diff --git a/src/content/docs/mcp/guides/add-a-tool.mdx b/src/content/docs/mcp/guides/add-a-tool.mdx
index 0ca6da0..e79ac3d 100644
--- a/src/content/docs/mcp/guides/add-a-tool.mdx
+++ b/src/content/docs/mcp/guides/add-a-tool.mdx
@@ -89,7 +89,7 @@ This guide adds `authors_get`, which returns one author with every review they'v
```
```bash
- curl -X POST "http://localhost:8080/mcp" \
+ curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"authors_get","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5"}}}'
```
diff --git a/src/content/docs/mcp/guides/connect-a-client.mdx b/src/content/docs/mcp/guides/connect-a-client.mdx
index 9cfdfef..ce93e20 100644
--- a/src/content/docs/mcp/guides/connect-a-client.mdx
+++ b/src/content/docs/mcp/guides/connect-a-client.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
-The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with `dotnet run --project ./src/Presentation` or Docker Compose, and give the client `http://localhost:8080/mcp`. Both listen there.
+The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with Docker Compose and give the client `http://localhost:8080/mcp`. With `dotnet run` the address is `https://localhost:8081/mcp`, which needs a client that trusts the .NET development certificate, so Compose is the easier one to connect to.
No client to hand? Docker Compose can start one for you: see [Chat with it](../run-locally/#chat-with-it).
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index 9eab124..5970301 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose. The server is at `/mcp`.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once. The server is at `/mcp`.
```bash
@@ -58,7 +58,7 @@ The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the of
- **Resources** lets you read `movies://top-rated`, `server://version` or any `movies://`, `authors://` or `reviews://` URI, and the ids autocomplete as you type an id or a title.
- **Prompts** renders each prompt with its arguments, and the title, name and id arguments autocomplete from the database.
-Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `http://localhost:8080/mcp`.
+Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `https://localhost:8081/mcp`. Node doesn't read the system's trusted certificates by default, so if it can't connect, use Docker Compose instead.
## Chat with it
@@ -83,21 +83,21 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Pre
## Try the tools from the terminal
-The server is stateless, so a single `tools/call` works without an `initialize` first. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
+The server is stateless, so a single `tools/call` works without an `initialize` first. These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080/mcp` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
```bash
# One movie, with every review
-curl -X POST "http://localhost:8080/mcp" \
+curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}}}'
# Write a review
-curl -X POST "http://localhost:8080/mcp" \
+curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}}}'
# Read a resource
-curl -X POST "http://localhost:8080/mcp" \
+curl -X POST "https://localhost:8081/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
```
diff --git a/src/content/docs/mcp/guides/secure-the-server.mdx b/src/content/docs/mcp/guides/secure-the-server.mdx
index dd49da8..bddd0af 100644
--- a/src/content/docs/mcp/guides/secure-the-server.mdx
+++ b/src/content/docs/mcp/guides/secure-the-server.mdx
@@ -42,7 +42,7 @@ Out of the box the server runs wide open, and there's no authentication code in
3. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "http://localhost:8080/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:8081/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
diff --git a/src/content/docs/minimalapi/getting-started.mdx b/src/content/docs/minimalapi/getting-started.mdx
index 8de91fe..efe2ede 100644
--- a/src/content/docs/minimalapi/getting-started.mdx
+++ b/src/content/docs/minimalapi/getting-started.mdx
@@ -55,12 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
+5. Open the Scalar API reference at [https://localhost:8081/scalar](https://localhost:8081/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "http://localhost:8080/api/movies?query=space&limit=2"
+ curl "https://localhost:8081/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
index 3dc998e..de14fe9 100644
--- a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
@@ -101,7 +101,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 2707e7e..2248a06 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`, the same port as Docker Compose.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
@@ -62,17 +62,17 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Pre
## Try the endpoints
-The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/minimalapi/guides/secure-the-api.mdx b/src/content/docs/minimalapi/guides/secure-the-api.mdx
index ce5fac6..47aa2c7 100644
--- a/src/content/docs/minimalapi/guides/secure-the-api.mdx
+++ b/src/content/docs/minimalapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "http://localhost:8080/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:8081/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/minimalapi/reference/endpoints.mdx b/src/content/docs/minimalapi/reference/endpoints.mdx
index 325013c..0816b4e 100644
--- a/src/content/docs/minimalapi/reference/endpoints.mdx
+++ b/src/content/docs/minimalapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8081`; with Docker Compose it's `http://localhost:8080`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab2
```
```bash
-curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:8081/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/webapi/getting-started.mdx b/src/content/docs/webapi/getting-started.mdx
index 52aacd0..fe8f2c0 100644
--- a/src/content/docs/webapi/getting-started.mdx
+++ b/src/content/docs/webapi/getting-started.mdx
@@ -55,12 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
-5. Open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+ It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+
+5. Open the Scalar API reference at [https://localhost:8081/scalar](https://localhost:8081/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "http://localhost:8080/api/movies?query=space&limit=2"
+ curl "https://localhost:8081/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/webapi/guides/add-an-endpoint.mdx b/src/content/docs/webapi/guides/add-an-endpoint.mdx
index 957173b..9c40c57 100644
--- a/src/content/docs/webapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/webapi/guides/add-an-endpoint.mdx
@@ -95,7 +95,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index abb95a4..2248a06 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -17,7 +17,7 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [http://localhost:8080](http://localhost:8080) and `https://localhost:8081`.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
@@ -62,17 +62,17 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Pre
## Try the endpoints
-The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "http://localhost:8080/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/webapi/guides/secure-the-api.mdx b/src/content/docs/webapi/guides/secure-the-api.mdx
index 9f4fa29..834308f 100644
--- a/src/content/docs/webapi/guides/secure-the-api.mdx
+++ b/src/content/docs/webapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "http://localhost:8080/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:8081/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/webapi/reference/endpoints.mdx b/src/content/docs/webapi/reference/endpoints.mdx
index 87d3b5f..4edc682 100644
--- a/src/content/docs/webapi/reference/endpoints.mdx
+++ b/src/content/docs/webapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8081`; with Docker Compose it's `http://localhost:8080`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/movies?query=space&limit=2"
+curl "https://localhost:8081/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "http://localhost:8080/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab2
```
```bash
-curl "http://localhost:8080/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:8081/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "http://localhost:8080/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
From 5a6d00cbe3c31c639335a4b7f4bb66f966e4fa30 Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:03:49 +0000
Subject: [PATCH 3/6] Name both dotnet run ports in the Stuck tips
---
src/content/docs/graphql/guides/run-locally.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 2 +-
src/content/docs/minimalapi/guides/run-locally.mdx | 2 +-
src/content/docs/webapi/guides/run-locally.mdx | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 6d44587..1c02489 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index 5970301..f98e078 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -136,5 +136,5 @@ docker build . --target run --tag acme-movies:latest \
The `server://version` resource and the `serverInfo` in the initialize response then report `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 2248a06..c4751cf 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index 2248a06..c4751cf 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
From 6e30f04635a253e96dbff2b738a3c1fae814ab14 Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:16:55 +0000
Subject: [PATCH 4/6] Show each template's own block of local ports
The templates now publish everything in their own block of ten, Minimal API
8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750, so the
docs use those ports for Docker Compose and dotnet run, and the configuration
references explain the scheme.
---
src/content/docs/graphql/getting-started.mdx | 6 ++--
.../docs/graphql/guides/add-a-field.mdx | 2 +-
src/content/docs/graphql/guides/database.mdx | 2 +-
.../docs/graphql/guides/run-locally.mdx | 14 ++++-----
.../docs/graphql/guides/secure-the-api.mdx | 2 +-
src/content/docs/graphql/index.mdx | 2 +-
.../docs/graphql/reference/configuration.mdx | 10 ++++---
src/content/docs/lambda/getting-started.mdx | 4 +--
.../docs/lambda/guides/add-a-function.mdx | 4 +--
src/content/docs/lambda/guides/database.mdx | 2 +-
.../docs/lambda/guides/run-locally.mdx | 14 ++++-----
.../docs/lambda/reference/configuration.mdx | 8 +++--
src/content/docs/mcp/getting-started.mdx | 6 ++--
src/content/docs/mcp/guides/add-a-tool.mdx | 2 +-
.../docs/mcp/guides/connect-a-client.mdx | 10 +++----
src/content/docs/mcp/guides/database.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 30 +++++++++----------
.../docs/mcp/guides/secure-the-server.mdx | 2 +-
src/content/docs/mcp/index.mdx | 2 +-
.../docs/mcp/reference/configuration.mdx | 12 ++++----
.../docs/minimalapi/getting-started.mdx | 6 ++--
.../minimalapi/guides/add-an-endpoint.mdx | 2 +-
.../docs/minimalapi/guides/database.mdx | 2 +-
.../docs/minimalapi/guides/run-locally.mdx | 22 +++++++-------
.../docs/minimalapi/guides/secure-the-api.mdx | 2 +-
src/content/docs/minimalapi/index.mdx | 2 +-
.../minimalapi/reference/configuration.mdx | 10 ++++---
.../docs/minimalapi/reference/endpoints.mdx | 10 +++----
src/content/docs/webapi/getting-started.mdx | 6 ++--
.../docs/webapi/guides/add-an-endpoint.mdx | 2 +-
src/content/docs/webapi/guides/database.mdx | 2 +-
.../docs/webapi/guides/run-locally.mdx | 22 +++++++-------
.../docs/webapi/guides/secure-the-api.mdx | 2 +-
src/content/docs/webapi/index.mdx | 2 +-
.../docs/webapi/reference/configuration.mdx | 10 ++++---
.../docs/webapi/reference/endpoints.mdx | 10 +++----
36 files changed, 129 insertions(+), 119 deletions(-)
diff --git a/src/content/docs/graphql/getting-started.mdx b/src/content/docs/graphql/getting-started.mdx
index d5589dc..46d110f 100644
--- a/src/content/docs/graphql/getting-started.mdx
+++ b/src/content/docs/graphql/getting-started.mdx
@@ -55,9 +55,9 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:8731`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
-5. Open Nitro, the GraphQL IDE, at [https://localhost:8081/graphql](https://localhost:8081/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
+5. Open Nitro, the GraphQL IDE, at [https://localhost:8731/graphql](https://localhost:8731/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
```graphql
{
@@ -72,7 +72,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or ask from the terminal.
```bash
- curl -X POST "https://localhost:8081/graphql" -H "Content-Type: application/json" \
+ curl -X POST "https://localhost:8731/graphql" -H "Content-Type: application/json" \
-d '{"query":"{ movies(query: \"space\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
```
diff --git a/src/content/docs/graphql/guides/add-a-field.mdx b/src/content/docs/graphql/guides/add-a-field.mdx
index 3910ff9..387e8ac 100644
--- a/src/content/docs/graphql/guides/add-a-field.mdx
+++ b/src/content/docs/graphql/guides/add-a-field.mdx
@@ -60,7 +60,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```bash
dotnet run --project ./src/Presentation
- curl "https://localhost:8081/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
+ curl "https://localhost:8731/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
```
The diff is the one field, described:
diff --git a/src/content/docs/graphql/guides/database.mdx b/src/content/docs/graphql/guides/database.mdx
index 2d620b6..a4fa171 100644
--- a/src/content/docs/graphql/guides/database.mdx
+++ b/src/content/docs/graphql/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 8730:8080 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 1c02489..5cc4c4a 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8731](https://localhost:8731), and on `https://localhost:8731`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8080](http://localhost:8080). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8730](http://localhost:8730). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 8730:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -44,19 +44,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/graphql?sdl` | The schema in SDL, the same text the snapshot test checks |
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
-| [http://localhost:18888](http://localhost:18888) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:8732](http://localhost:8732) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888). Open **Traces** to follow a request from the HTTP request through the GraphQL operation to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8732](http://localhost:8732). Open **Traces** to follow a request from the HTTP request through the GraphQL operation to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8733 dotnet run --project ./src/Presentation
```
## Try the schema
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/graphql/guides/secure-the-api.mdx b/src/content/docs/graphql/guides/secure-the-api.mdx
index 8561b56..615d9f6 100644
--- a/src/content/docs/graphql/guides/secure-the-api.mdx
+++ b/src/content/docs/graphql/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "https://localhost:8081/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:8731/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -d '{"query":"{ movies { totalCount } }"}'
```
diff --git a/src/content/docs/graphql/index.mdx b/src/content/docs/graphql/index.mdx
index 4c012a2..295e02c 100644
--- a/src/content/docs/graphql/index.mdx
+++ b/src/content/docs/graphql/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open Nitro, the GraphQL IDE, at [http://localhost:8080/graphql](http://localhost:8080/graphql) and start querying. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888), because `Console.WriteLine` is not an observability strategy.
+Then open Nitro, the GraphQL IDE, at [http://localhost:8730/graphql](http://localhost:8730/graphql) and start querying. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8732](http://localhost:8732), because `Console.WriteLine` is not an observability strategy.
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and the movie, author and review queries and the review mutations need a JWT bearer token. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/graphql/reference/configuration.mdx b/src/content/docs/graphql/reference/configuration.mdx
index 6c44903..fd0e873 100644
--- a/src/content/docs/graphql/reference/configuration.mdx
+++ b/src/content/docs/graphql/reference/configuration.mdx
@@ -34,9 +34,11 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8080 |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 4317 (gRPC), 4318 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 18888 |
+| `api` | The image, built from the `run` stage | 8730 (8080 inside the container) |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8733 (gRPC), 8734 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8732 |
+
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8730 to 8739 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -55,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8730` and `https://localhost:8731`.
diff --git a/src/content/docs/lambda/getting-started.mdx b/src/content/docs/lambda/getting-started.mdx
index 8714c46..d92050f 100644
--- a/src/content/docs/lambda/getting-started.mdx
+++ b/src/content/docs/lambda/getting-started.mdx
@@ -55,10 +55,10 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) an
docker compose up -d --build
```
-5. Invoke the function. The emulator listens on port 9000 and answers the same way Lambda does.
+5. Invoke the function. The emulator listens on port 8750 and answers the same way Lambda does.
```bash
- curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
+ curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" \
-d '{"query":"space","limit":2}'
```
diff --git a/src/content/docs/lambda/guides/add-a-function.mdx b/src/content/docs/lambda/guides/add-a-function.mdx
index aa5c5b2..3d4b68d 100644
--- a/src/content/docs/lambda/guides/add-a-function.mdx
+++ b/src/content/docs/lambda/guides/add-a-function.mdx
@@ -103,10 +103,10 @@ This guide adds a `GetMovie` function that returns one movie with all of its rev
```bash
docker build . --target run --tag acme-reviews:local
- docker run --rm -p 9000:8080 -e Database__SeedSampleData=true acme-reviews:local \
+ docker run --rm -p 8750:8080 -e Database__SeedSampleData=true acme-reviews:local \
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetMovie_Generated::GetMovie"
- curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
+ curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" \
-d '{"id":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}'
```
diff --git a/src/content/docs/lambda/guides/database.mdx b/src/content/docs/lambda/guides/database.mdx
index e9db935..8e231f1 100644
--- a/src/content/docs/lambda/guides/database.mdx
+++ b/src/content/docs/lambda/guides/database.mdx
@@ -22,7 +22,7 @@ Every time a container starts, the handler applies any pending Entity Framework
Set `ConnectionStrings__MovieReviews`. To keep everything in memory:
```bash
-docker run --rm -p 9000:8080 \
+docker run --rm -p 8750:8080 \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
-e Database__SeedSampleData=true \
acme-reviews:local
diff --git a/src/content/docs/lambda/guides/run-locally.mdx b/src/content/docs/lambda/guides/run-locally.mdx
index 5eda99b..0f91893 100644
--- a/src/content/docs/lambda/guides/run-locally.mdx
+++ b/src/content/docs/lambda/guides/run-locally.mdx
@@ -17,12 +17,12 @@ The image is built on the [AWS Lambda .NET base image](https://gallery.ecr.aws/l
docker compose up -d --build
```
- Compose builds the `run` stage, publishes port 9000, turns on sample data, and starts an OpenTelemetry Collector and the Aspire dashboard next to it. Shut it down with `docker compose down`.
+ Compose builds the `run` stage, publishes port 8750, turns on sample data, and starts an OpenTelemetry Collector and the Aspire dashboard next to it. Shut it down with `docker compose down`.
```bash
docker build . --target run --tag acme-reviews:local
- docker run --rm -p 9000:8080 -e Database__SeedSampleData=true acme-reviews:local
+ docker run --rm -p 8750:8080 -e Database__SeedSampleData=true acme-reviews:local
```
@@ -30,12 +30,12 @@ The image is built on the [AWS Lambda .NET base image](https://gallery.ecr.aws/l
Then invoke it from another terminal:
```bash
-curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"limit":5}'
+curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" -d '{"limit":5}'
```
## See the telemetry
-With Docker Compose, the function sends its traces, metrics and logs over OTLP to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), which forwards them to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888). Each invocation is one trace: the function's span from the OpenTelemetry Lambda wrapper, with the use case under it. Traces and metrics are flushed before the function returns, so they're there by the time `curl` is. The logs still go to the console too, so `docker compose logs function` works as it always did. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+With Docker Compose, the function sends its traces, metrics and logs over OTLP to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), which forwards them to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8752](http://localhost:8752). Each invocation is one trace: the function's span from the OpenTelemetry Lambda wrapper, with the use case under it. Traces and metrics are flushed before the function returns, so they're there by the time `curl` is. The logs still go to the console too, so `docker compose logs function` works as it always did. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
Run the image on its own and nothing is exported, because `OTEL_EXPORTER_OTLP_ENDPOINT` isn't set. Set it to send the telemetry to a collector of your own.
@@ -44,10 +44,10 @@ Run the image on its own and nothing is exported, because `OTEL_EXPORTER_OTLP_EN
The image runs `GetMovies` by default. The same image also has `GetAuthors` and `GetReviews`. Pass the function's full handler name to `docker run` to start a different one.
```bash
-docker run --rm -p 9000:8080 -e Database__SeedSampleData=true acme-reviews:local \
+docker run --rm -p 8750:8080 -e Database__SeedSampleData=true acme-reviews:local \
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetReviews_Generated::GetReviews"
-curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
+curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" \
-d '{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","limit":2}'
```
@@ -85,5 +85,5 @@ docker build . --target run --tag acme-reviews:latest \
```
diff --git a/src/content/docs/lambda/reference/configuration.mdx b/src/content/docs/lambda/reference/configuration.mdx
index 8f8261a..81cb5a6 100644
--- a/src/content/docs/lambda/reference/configuration.mdx
+++ b/src/content/docs/lambda/reference/configuration.mdx
@@ -21,9 +21,11 @@ sidebar:
| Service | What it is | Port |
| --- | --- | --- |
-| `function` | The image, built from the `run` stage, behind the runtime interface emulator | 9000 |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 4317 (gRPC), 4318 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 18888 |
+| `function` | The image, built from the `run` stage, behind the runtime interface emulator | 8750 (8080 inside the container) |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8753 (gRPC), 8754 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8752 |
+
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8750 to 8759 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
## Docker build arguments
diff --git a/src/content/docs/mcp/getting-started.mdx b/src/content/docs/mcp/getting-started.mdx
index b58d294..8c97a66 100644
--- a/src/content/docs/mcp/getting-started.mdx
+++ b/src/content/docs/mcp/getting-started.mdx
@@ -55,12 +55,12 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:8721`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
5. Call a tool. The server speaks Streamable HTTP at `/mcp`, and because it's stateless you can send a single JSON-RPC request with nothing before it.
```bash
- curl -X POST "https://localhost:8081/mcp" \
+ curl -X POST "https://localhost:8721/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"space","limit":2}}}'
```
@@ -86,7 +86,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
docker compose up -d --build
```
- Open [http://localhost:6274](http://localhost:6274), connect, and the **Tools**, **Resources** and **Prompts** tabs show everything the server offers, with a form for each.
+ Open [http://localhost:8725](http://localhost:8725), connect, and the **Tools**, **Resources** and **Prompts** tabs show everything the server offers, with a form for each.
7. Run the tests while you're here.
diff --git a/src/content/docs/mcp/guides/add-a-tool.mdx b/src/content/docs/mcp/guides/add-a-tool.mdx
index e79ac3d..7aa3037 100644
--- a/src/content/docs/mcp/guides/add-a-tool.mdx
+++ b/src/content/docs/mcp/guides/add-a-tool.mdx
@@ -89,7 +89,7 @@ This guide adds `authors_get`, which returns one author with every review they'v
```
```bash
- curl -X POST "https://localhost:8081/mcp" \
+ curl -X POST "https://localhost:8721/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"authors_get","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5"}}}'
```
diff --git a/src/content/docs/mcp/guides/connect-a-client.mdx b/src/content/docs/mcp/guides/connect-a-client.mdx
index ce93e20..815abe5 100644
--- a/src/content/docs/mcp/guides/connect-a-client.mdx
+++ b/src/content/docs/mcp/guides/connect-a-client.mdx
@@ -7,14 +7,14 @@ sidebar:
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
-The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with Docker Compose and give the client `http://localhost:8080/mcp`. With `dotnet run` the address is `https://localhost:8081/mcp`, which needs a client that trusts the .NET development certificate, so Compose is the easier one to connect to.
+The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with Docker Compose and give the client `http://localhost:8720/mcp`. With `dotnet run` the address is `https://localhost:8721/mcp`, which needs a client that trusts the .NET development certificate, so Compose is the easier one to connect to.
No client to hand? Docker Compose can start one for you: see [Chat with it](../run-locally/#chat-with-it).
```bash
- claude mcp add --transport http acme-movies http://localhost:8080/mcp
+ claude mcp add --transport http acme-movies http://localhost:8720/mcp
```
Then ask it something like "which movie has the best reviews?" and watch it call `movies_search` and `movies_get` on its own.
@@ -28,7 +28,7 @@ No client to hand? Docker Compose can start one for you: see [Chat with it](../r
```json
{
"servers": {
- "acme-movies": { "type": "http", "url": "http://localhost:8080/mcp" }
+ "acme-movies": { "type": "http", "url": "http://localhost:8720/mcp" }
}
}
```
@@ -39,7 +39,7 @@ No client to hand? Docker Compose can start one for you: see [Chat with it](../r
```json
{
"mcpServers": {
- "acme-movies": { "url": "http://localhost:8080/mcp" }
+ "acme-movies": { "url": "http://localhost:8720/mcp" }
}
}
```
@@ -51,7 +51,7 @@ No client to hand? Docker Compose can start one for you: see [Chat with it](../r
using ModelContextProtocol.Client;
await using var client = await McpClient.CreateAsync(
- new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:8080/mcp") }));
+ new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:8720/mcp") }));
var result = await client.CallToolAsync("movies_search", new Dictionary { ["query"] = "space" });
```
diff --git a/src/content/docs/mcp/guides/database.mdx b/src/content/docs/mcp/guides/database.mdx
index d532094..e6dcc30 100644
--- a/src/content/docs/mcp/guides/database.mdx
+++ b/src/content/docs/mcp/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 8720:8080 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index f98e078..e9afd69 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once. The server is at `/mcp`.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8721](https://localhost:8721), and on `https://localhost:8721`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once. The server is at `/mcp`.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts the MCP Inspector, an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the server on [http://localhost:8080/mcp](http://localhost:8080/mcp). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts the MCP Inspector, an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the server on [http://localhost:8720/mcp](http://localhost:8720/mcp). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 8720:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -44,9 +44,9 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
| `/.well-known/oauth-protected-resource/mcp` | Protected resource metadata, only when [authentication is on](../secure-the-server/) |
-| [http://localhost:6274](http://localhost:6274) | The MCP Inspector, when running with Docker Compose |
-| [http://localhost:3000](http://localhost:3000) | Open WebUI, when running with Docker Compose and the `demo` profile |
-| [http://localhost:18888](http://localhost:18888) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:8725](http://localhost:8725) | The MCP Inspector, when running with Docker Compose |
+| [http://localhost:8726](http://localhost:8726) | Open WebUI, when running with Docker Compose and the `demo` profile |
+| [http://localhost:8722](http://localhost:8722) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
@@ -58,7 +58,7 @@ The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the of
- **Resources** lets you read `movies://top-rated`, `server://version` or any `movies://`, `authors://` or `reviews://` URI, and the ids autocomplete as you type an id or a title.
- **Prompts** renders each prompt with its arguments, and the title, name and id arguments autocomplete from the database.
-Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `https://localhost:8081/mcp`. Node doesn't read the system's trusted certificates by default, so if it can't connect, use Docker Compose instead.
+Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `https://localhost:8721/mcp`. Node doesn't read the system's trusted certificates by default, so if it can't connect, use Docker Compose instead.
## Chat with it
@@ -68,36 +68,36 @@ Want to ask for things in plain English? The optional `demo` profile adds [Open
docker compose --profile demo up -d --build
```
-Open [http://localhost:3000](http://localhost:3000), dismiss the release notes and ask it to list the movies. It uses Kilo's free gateway as the model, so there's no account or key, but it only works online, and your prompts and tool results go to Kilo, a third party. Keep it to sample data. Solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. Stop it with `docker compose --profile demo down`.
+Open [http://localhost:8726](http://localhost:8726), dismiss the release notes and ask it to list the movies. It uses Kilo's free gateway as the model, so there's no account or key, but it only works online, and your prompts and tool results go to Kilo, a third party. Keep it to sample data. Solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. Stop it with `docker compose --profile demo down`.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the server, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888). Open **Traces** to follow a request from the HTTP request through the tool call to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the server, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8722](http://localhost:8722). Open **Traces** to follow a request from the HTTP request through the tool call to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The server only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the server doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the server at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8723 dotnet run --project ./src/Presentation
```
## Try the tools from the terminal
-The server is stateless, so a single `tools/call` works without an `initialize` first. These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080/mcp` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
+The server is stateless, so a single `tools/call` works without an `initialize` first. These use the `dotnet run` address; with Docker Compose, use `http://localhost:8720/mcp` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
```bash
# One movie, with every review
-curl -X POST "https://localhost:8081/mcp" \
+curl -X POST "https://localhost:8721/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}}}'
# Write a review
-curl -X POST "https://localhost:8081/mcp" \
+curl -X POST "https://localhost:8721/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}}}'
# Read a resource
-curl -X POST "https://localhost:8081/mcp" \
+curl -X POST "https://localhost:8721/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
```
@@ -136,5 +136,5 @@ docker build . --target run --tag acme-movies:latest \
The `server://version` resource and the `serverInfo` in the initialize response then report `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/mcp/guides/secure-the-server.mdx b/src/content/docs/mcp/guides/secure-the-server.mdx
index bddd0af..157222d 100644
--- a/src/content/docs/mcp/guides/secure-the-server.mdx
+++ b/src/content/docs/mcp/guides/secure-the-server.mdx
@@ -42,7 +42,7 @@ Out of the box the server runs wide open, and there's no authentication code in
3. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "https://localhost:8081/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:8721/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
diff --git a/src/content/docs/mcp/index.mdx b/src/content/docs/mcp/index.mdx
index 197c6b6..02615ab 100644
--- a/src/content/docs/mcp/index.mdx
+++ b/src/content/docs/mcp/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open the MCP Inspector at [http://localhost:6274](http://localhost:6274). It's already pointed at the server on [http://localhost:8080/mcp](http://localhost:8080/mcp), so connect, open **Tools** and start clicking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888).
+Then open the MCP Inspector at [http://localhost:8725](http://localhost:8725). It's already pointed at the server on [http://localhost:8720/mcp](http://localhost:8720/mcp), so connect, open **Tools** and start clicking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8722](http://localhost:8722).
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and `/mcp` needs a bearer token, with protected resource metadata so MCP clients can find your authorization server and sign in. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/mcp/reference/configuration.mdx b/src/content/docs/mcp/reference/configuration.mdx
index 71946eb..2ffda63 100644
--- a/src/content/docs/mcp/reference/configuration.mdx
+++ b/src/content/docs/mcp/reference/configuration.mdx
@@ -35,10 +35,12 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8080 |
-| `inspector` | The MCP Inspector, pointed at the server | 6274, on localhost only |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 4317 (gRPC), 4318 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 18888 |
+| `api` | The image, built from the `run` stage | 8720 (8080 inside the container) |
+| `inspector` | The MCP Inspector, pointed at the server | 8725, on localhost only |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8723 (gRPC), 8724 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8722 |
+
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8720 to 8729 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports, except the Inspector, which only accepts calls from the origin it listens on and so uses 8725 inside too.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -57,4 +59,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8720` and `https://localhost:8721`.
diff --git a/src/content/docs/minimalapi/getting-started.mdx b/src/content/docs/minimalapi/getting-started.mdx
index efe2ede..3d6dbe9 100644
--- a/src/content/docs/minimalapi/getting-started.mdx
+++ b/src/content/docs/minimalapi/getting-started.mdx
@@ -55,14 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:8711`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
-5. Open the Scalar API reference at [https://localhost:8081/scalar](https://localhost:8081/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+5. Open the Scalar API reference at [https://localhost:8711/scalar](https://localhost:8711/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "https://localhost:8081/api/movies?query=space&limit=2"
+ curl "https://localhost:8711/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
index de14fe9..268a48c 100644
--- a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
@@ -101,7 +101,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:8711/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/minimalapi/guides/database.mdx b/src/content/docs/minimalapi/guides/database.mdx
index 2d620b6..a065734 100644
--- a/src/content/docs/minimalapi/guides/database.mdx
+++ b/src/content/docs/minimalapi/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 8710:8080 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index c4751cf..414dabd 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8711](https://localhost:8711), and on `https://localhost:8711`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8080](http://localhost:8080). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8710](http://localhost:8710). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 8710:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -45,34 +45,34 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
| `/api/version` | The version stamped into the build. Needs a token when authentication is on |
-| [http://localhost:18888](http://localhost:18888) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:8712](http://localhost:8712) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8712](http://localhost:8712). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8713 dotnet run --project ./src/Presentation
```
## Try the endpoints
-These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:8710` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "https://localhost:8081/api/movies?query=space&limit=2"
+curl "https://localhost:8711/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:8711/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8711/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/minimalapi/guides/secure-the-api.mdx b/src/content/docs/minimalapi/guides/secure-the-api.mdx
index 47aa2c7..dffaa61 100644
--- a/src/content/docs/minimalapi/guides/secure-the-api.mdx
+++ b/src/content/docs/minimalapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "https://localhost:8081/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:8711/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/minimalapi/index.mdx b/src/content/docs/minimalapi/index.mdx
index 84bdd69..ab1bbd6 100644
--- a/src/content/docs/minimalapi/index.mdx
+++ b/src/content/docs/minimalapi/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888), because `Console.WriteLine` is not an observability strategy.
+Then open the Scalar API reference at [http://localhost:8710/scalar](http://localhost:8710/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8712](http://localhost:8712), because `Console.WriteLine` is not an observability strategy.
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and every movie, author and review endpoint needs a JWT bearer token. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/minimalapi/reference/configuration.mdx b/src/content/docs/minimalapi/reference/configuration.mdx
index 863ca90..a105192 100644
--- a/src/content/docs/minimalapi/reference/configuration.mdx
+++ b/src/content/docs/minimalapi/reference/configuration.mdx
@@ -34,9 +34,11 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8080 |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 4317 (gRPC), 4318 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 18888 |
+| `api` | The image, built from the `run` stage | 8710 (8080 inside the container) |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8713 (gRPC), 8714 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8712 |
+
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8710 to 8719 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -55,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8710` and `https://localhost:8711`.
diff --git a/src/content/docs/minimalapi/reference/endpoints.mdx b/src/content/docs/minimalapi/reference/endpoints.mdx
index 0816b4e..cf61b6d 100644
--- a/src/content/docs/minimalapi/reference/endpoints.mdx
+++ b/src/content/docs/minimalapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8081`; with Docker Compose it's `http://localhost:8080`.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8711`; with Docker Compose it's `http://localhost:8710`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8081/api/movies?query=space&limit=2"
+curl "https://localhost:8711/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:8711/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab
```
```bash
-curl "https://localhost:8081/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:8711/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8711/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/webapi/getting-started.mdx b/src/content/docs/webapi/getting-started.mdx
index fe8f2c0..6d71600 100644
--- a/src/content/docs/webapi/getting-started.mdx
+++ b/src/content/docs/webapi/getting-started.mdx
@@ -55,14 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8081`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:8741`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
-5. Open the Scalar API reference at [https://localhost:8081/scalar](https://localhost:8081/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+5. Open the Scalar API reference at [https://localhost:8741/scalar](https://localhost:8741/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "https://localhost:8081/api/movies?query=space&limit=2"
+ curl "https://localhost:8741/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/webapi/guides/add-an-endpoint.mdx b/src/content/docs/webapi/guides/add-an-endpoint.mdx
index 9c40c57..3704c54 100644
--- a/src/content/docs/webapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/webapi/guides/add-an-endpoint.mdx
@@ -95,7 +95,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:8741/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/webapi/guides/database.mdx b/src/content/docs/webapi/guides/database.mdx
index 2d620b6..c86b948 100644
--- a/src/content/docs/webapi/guides/database.mdx
+++ b/src/content/docs/webapi/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 8740:8080 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index c4751cf..a98e60e 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8081](https://localhost:8081), and on `https://localhost:8081`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8741](https://localhost:8741), and on `https://localhost:8741`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8080](http://localhost:8080). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8740](http://localhost:8740). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8080:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 8740:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -45,34 +45,34 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
| `/api/version` | The version stamped into the build. Needs a token when authentication is on |
-| [http://localhost:18888](http://localhost:18888) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:8742](http://localhost:8742) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8742](http://localhost:8742). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8743 dotnet run --project ./src/Presentation
```
## Try the endpoints
-These use the `dotnet run` address; with Docker Compose, use `http://localhost:8080` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:8740` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "https://localhost:8081/api/movies?query=space&limit=2"
+curl "https://localhost:8741/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "https://localhost:8081/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:8741/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8741/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/webapi/guides/secure-the-api.mdx b/src/content/docs/webapi/guides/secure-the-api.mdx
index 834308f..fc800b7 100644
--- a/src/content/docs/webapi/guides/secure-the-api.mdx
+++ b/src/content/docs/webapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "https://localhost:8081/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:8741/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/webapi/index.mdx b/src/content/docs/webapi/index.mdx
index c351ac8..053593f 100644
--- a/src/content/docs/webapi/index.mdx
+++ b/src/content/docs/webapi/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open the Scalar API reference at [http://localhost:8080/scalar](http://localhost:8080/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:18888](http://localhost:18888), because `Console.WriteLine` is not an observability strategy.
+Then open the Scalar API reference at [http://localhost:8740/scalar](http://localhost:8740/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8742](http://localhost:8742), because `Console.WriteLine` is not an observability strategy.
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and every movie, author and review endpoint needs a JWT bearer token. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/webapi/reference/configuration.mdx b/src/content/docs/webapi/reference/configuration.mdx
index 1f617ee..ce4be2b 100644
--- a/src/content/docs/webapi/reference/configuration.mdx
+++ b/src/content/docs/webapi/reference/configuration.mdx
@@ -34,9 +34,11 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8080 |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 4317 (gRPC), 4318 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 18888 |
+| `api` | The image, built from the `run` stage | 8740 (8080 inside the container) |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8743 (gRPC), 8744 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8742 |
+
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8740 to 8749 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -55,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8080` and `https://localhost:8081`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8740` and `https://localhost:8741`.
diff --git a/src/content/docs/webapi/reference/endpoints.mdx b/src/content/docs/webapi/reference/endpoints.mdx
index 4edc682..528be2d 100644
--- a/src/content/docs/webapi/reference/endpoints.mdx
+++ b/src/content/docs/webapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8081`; with Docker Compose it's `http://localhost:8080`.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8741`; with Docker Compose it's `http://localhost:8740`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8081/api/movies?query=space&limit=2"
+curl "https://localhost:8741/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:8741/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "https://localhost:8081/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab
```
```bash
-curl "https://localhost:8081/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:8741/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "https://localhost:8081/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:8741/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
From b6193a6579bf8567335b4e677269068c6343718d Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:50:07 +0000
Subject: [PATCH 5/6] Move the docs to the 73x0 blocks and the matching
container port
The templates now use 7310, 7320, 7330, 7340 and 7350, and each app listens
on that port inside its container too, so docker run and the deploy guides
use it as well. Lambda's emulator stays on 8080 inside.
---
src/content/docs/graphql/getting-started.mdx | 6 ++--
.../docs/graphql/guides/add-a-field.mdx | 2 +-
src/content/docs/graphql/guides/database.mdx | 2 +-
.../docs/graphql/guides/run-locally.mdx | 14 ++++----
.../docs/graphql/guides/secure-the-api.mdx | 2 +-
.../graphql/guides/ship-the-container.mdx | 4 +--
src/content/docs/graphql/index.mdx | 2 +-
.../docs/graphql/reference/configuration.mdx | 12 +++----
src/content/docs/lambda/getting-started.mdx | 4 +--
.../docs/lambda/guides/add-a-function.mdx | 4 +--
src/content/docs/lambda/guides/database.mdx | 2 +-
.../docs/lambda/guides/run-locally.mdx | 14 ++++----
.../docs/lambda/reference/configuration.mdx | 8 ++---
src/content/docs/mcp/getting-started.mdx | 6 ++--
src/content/docs/mcp/guides/add-a-tool.mdx | 2 +-
.../docs/mcp/guides/connect-a-client.mdx | 10 +++---
src/content/docs/mcp/guides/database.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 32 +++++++++----------
.../docs/mcp/guides/secure-the-server.mdx | 2 +-
.../docs/mcp/guides/ship-the-container.mdx | 6 ++--
src/content/docs/mcp/index.mdx | 2 +-
.../docs/mcp/reference/configuration.mdx | 14 ++++----
.../docs/minimalapi/getting-started.mdx | 6 ++--
.../minimalapi/guides/add-an-endpoint.mdx | 2 +-
.../docs/minimalapi/guides/database.mdx | 2 +-
.../docs/minimalapi/guides/run-locally.mdx | 22 ++++++-------
.../docs/minimalapi/guides/secure-the-api.mdx | 2 +-
.../minimalapi/guides/ship-the-container.mdx | 4 +--
src/content/docs/minimalapi/index.mdx | 2 +-
.../minimalapi/reference/configuration.mdx | 12 +++----
.../docs/minimalapi/reference/endpoints.mdx | 10 +++---
src/content/docs/webapi/getting-started.mdx | 6 ++--
.../docs/webapi/guides/add-an-endpoint.mdx | 2 +-
src/content/docs/webapi/guides/database.mdx | 2 +-
.../docs/webapi/guides/run-locally.mdx | 22 ++++++-------
.../docs/webapi/guides/secure-the-api.mdx | 2 +-
.../docs/webapi/guides/ship-the-container.mdx | 4 +--
src/content/docs/webapi/index.mdx | 2 +-
.../docs/webapi/reference/configuration.mdx | 12 +++----
.../docs/webapi/reference/endpoints.mdx | 10 +++---
40 files changed, 138 insertions(+), 138 deletions(-)
diff --git a/src/content/docs/graphql/getting-started.mdx b/src/content/docs/graphql/getting-started.mdx
index 46d110f..ddb8339 100644
--- a/src/content/docs/graphql/getting-started.mdx
+++ b/src/content/docs/graphql/getting-started.mdx
@@ -55,9 +55,9 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8731`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:7331`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
-5. Open Nitro, the GraphQL IDE, at [https://localhost:8731/graphql](https://localhost:8731/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
+5. Open Nitro, the GraphQL IDE, at [https://localhost:7331/graphql](https://localhost:7331/graphql). The schema is on the left, with every type and field described, and the editor autocompletes as you type. Paste this in and press play:
```graphql
{
@@ -72,7 +72,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
6. Or ask from the terminal.
```bash
- curl -X POST "https://localhost:8731/graphql" -H "Content-Type: application/json" \
+ curl -X POST "https://localhost:7331/graphql" -H "Content-Type: application/json" \
-d '{"query":"{ movies(query: \"space\", first: 2) { totalCount nodes { id title reviewCount averageStars } } }"}'
```
diff --git a/src/content/docs/graphql/guides/add-a-field.mdx b/src/content/docs/graphql/guides/add-a-field.mdx
index 387e8ac..1990b25 100644
--- a/src/content/docs/graphql/guides/add-a-field.mdx
+++ b/src/content/docs/graphql/guides/add-a-field.mdx
@@ -60,7 +60,7 @@ This guide adds `bestReview` to the `Movie` type: the highest rated review of th
```bash
dotnet run --project ./src/Presentation
- curl "https://localhost:8731/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
+ curl "https://localhost:7331/graphql?sdl" > tests/Presentation.Tests.Integration/GraphQL/Schema/schema.graphql
```
The diff is the one field, described:
diff --git a/src/content/docs/graphql/guides/database.mdx b/src/content/docs/graphql/guides/database.mdx
index a4fa171..fca1b49 100644
--- a/src/content/docs/graphql/guides/database.mdx
+++ b/src/content/docs/graphql/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8730:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 7330:7330 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 5cc4c4a..939f2b1 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8731](https://localhost:8731), and on `https://localhost:8731`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:7331](https://localhost:7331), and on `https://localhost:7331`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8730](http://localhost:8730). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:7330](http://localhost:7330). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8730:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 7330:7330 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -44,19 +44,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/graphql?sdl` | The schema in SDL, the same text the snapshot test checks |
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
-| [http://localhost:8732](http://localhost:8732) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:7332](http://localhost:7332) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8732](http://localhost:8732). Open **Traces** to follow a request from the HTTP request through the GraphQL operation to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7332](http://localhost:7332). Open **Traces** to follow a request from the HTTP request through the GraphQL operation to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8733 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:7333 dotnet run --project ./src/Presentation
```
## Try the schema
@@ -117,5 +117,5 @@ docker build . --target run --tag acme-movies:latest \
The `version` query then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/graphql/guides/secure-the-api.mdx b/src/content/docs/graphql/guides/secure-the-api.mdx
index 615d9f6..6961112 100644
--- a/src/content/docs/graphql/guides/secure-the-api.mdx
+++ b/src/content/docs/graphql/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "https://localhost:8731/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:7331/graphql" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -d '{"query":"{ movies { totalCount } }"}'
```
diff --git a/src/content/docs/graphql/guides/ship-the-container.mdx b/src/content/docs/graphql/guides/ship-the-container.mdx
index 2567cae..099ae95 100644
--- a/src/content/docs/graphql/guides/ship-the-container.mdx
+++ b/src/content/docs/graphql/guides/ship-the-container.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Aside } from '@astrojs/starlight/components';
-The `run` stage of the `Dockerfile` is the whole API: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 8080 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
+The `run` stage of the `Dockerfile` is the whole API: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 7330 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
## Build it
@@ -58,5 +58,5 @@ The SQLite file lives in `/app/data`. Mount a volume there or, better, [point In
In a solution created with `--include-auth`, set `Authentication__Authority` and `Authentication__Audience` as environment variables to point each environment at its own authorization server. Without `--include-auth` there's nothing to turn on yet. The [security guide](../secure-the-api/) has the details.
diff --git a/src/content/docs/graphql/index.mdx b/src/content/docs/graphql/index.mdx
index 295e02c..4311e6b 100644
--- a/src/content/docs/graphql/index.mdx
+++ b/src/content/docs/graphql/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open Nitro, the GraphQL IDE, at [http://localhost:8730/graphql](http://localhost:8730/graphql) and start querying. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8732](http://localhost:8732), because `Console.WriteLine` is not an observability strategy.
+Then open Nitro, the GraphQL IDE, at [http://localhost:7330/graphql](http://localhost:7330/graphql) and start querying. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7332](http://localhost:7332), because `Console.WriteLine` is not an observability strategy.
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and the movie, author and review queries and the review mutations need a JWT bearer token. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/graphql/reference/configuration.mdx b/src/content/docs/graphql/reference/configuration.mdx
index fd0e873..aa489b3 100644
--- a/src/content/docs/graphql/reference/configuration.mdx
+++ b/src/content/docs/graphql/reference/configuration.mdx
@@ -12,7 +12,7 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Variable | Default | Description |
| --- | --- | --- |
| `DOTNET_ENVIRONMENT` | none | Layers `appsettings..json` over `appsettings.json`. The template ships `local` and `docker` |
-| `ASPNETCORE_URLS` | `http://+:8080` in the container | The addresses to listen on |
+| `ASPNETCORE_HTTP_PORTS` | `7330` in the container | The port to listen on |
| `ASPNETCORE_FORWARDEDHEADERS_ENABLED` | none | Set to `true` behind a proxy that ends TLS, so HTTPS redirection sees the original scheme |
| `ConnectionStrings__MovieReviews` | `Data Source=movie-reviews.db` | The SQLite connection string. `appsettings.docker.json` points it at `/app/data/movie-reviews.db` |
| `Database__SeedSampleData` | `false` | When `true`, seeds sample data into an empty database. On in `local` and `docker` |
@@ -34,11 +34,11 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8730 (8080 inside the container) |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8733 (gRPC), 8734 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8732 |
+| `api` | The image, built from the `run` stage | 7330, inside the container too |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 7333 (gRPC), 7334 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 7332 |
-The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8730 to 8739 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 7330 to 7339 and runs beside a solution made from any other template: Minimal API 7310, MCP 7320, GraphQL 7330, Web API 7340 and Lambda Function 7350. The app uses the same port inside its container; the dashboard and collector keep their usual ones.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -57,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8730` and `https://localhost:8731`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:7330` and `https://localhost:7331`.
diff --git a/src/content/docs/lambda/getting-started.mdx b/src/content/docs/lambda/getting-started.mdx
index d92050f..1966a5f 100644
--- a/src/content/docs/lambda/getting-started.mdx
+++ b/src/content/docs/lambda/getting-started.mdx
@@ -55,10 +55,10 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) an
docker compose up -d --build
```
-5. Invoke the function. The emulator listens on port 8750 and answers the same way Lambda does.
+5. Invoke the function. The emulator listens on port 7350 and answers the same way Lambda does.
```bash
- curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" \
+ curl -X POST "http://localhost:7350/2015-03-31/functions/function/invocations" \
-d '{"query":"space","limit":2}'
```
diff --git a/src/content/docs/lambda/guides/add-a-function.mdx b/src/content/docs/lambda/guides/add-a-function.mdx
index 3d4b68d..ac04c51 100644
--- a/src/content/docs/lambda/guides/add-a-function.mdx
+++ b/src/content/docs/lambda/guides/add-a-function.mdx
@@ -103,10 +103,10 @@ This guide adds a `GetMovie` function that returns one movie with all of its rev
```bash
docker build . --target run --tag acme-reviews:local
- docker run --rm -p 8750:8080 -e Database__SeedSampleData=true acme-reviews:local \
+ docker run --rm -p 7350:8080 -e Database__SeedSampleData=true acme-reviews:local \
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetMovie_Generated::GetMovie"
- curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" \
+ curl -X POST "http://localhost:7350/2015-03-31/functions/function/invocations" \
-d '{"id":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}'
```
diff --git a/src/content/docs/lambda/guides/database.mdx b/src/content/docs/lambda/guides/database.mdx
index 8e231f1..9aef504 100644
--- a/src/content/docs/lambda/guides/database.mdx
+++ b/src/content/docs/lambda/guides/database.mdx
@@ -22,7 +22,7 @@ Every time a container starts, the handler applies any pending Entity Framework
Set `ConnectionStrings__MovieReviews`. To keep everything in memory:
```bash
-docker run --rm -p 8750:8080 \
+docker run --rm -p 7350:8080 \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
-e Database__SeedSampleData=true \
acme-reviews:local
diff --git a/src/content/docs/lambda/guides/run-locally.mdx b/src/content/docs/lambda/guides/run-locally.mdx
index 0f91893..55a6b5f 100644
--- a/src/content/docs/lambda/guides/run-locally.mdx
+++ b/src/content/docs/lambda/guides/run-locally.mdx
@@ -17,12 +17,12 @@ The image is built on the [AWS Lambda .NET base image](https://gallery.ecr.aws/l
docker compose up -d --build
```
- Compose builds the `run` stage, publishes port 8750, turns on sample data, and starts an OpenTelemetry Collector and the Aspire dashboard next to it. Shut it down with `docker compose down`.
+ Compose builds the `run` stage, publishes port 7350, turns on sample data, and starts an OpenTelemetry Collector and the Aspire dashboard next to it. Shut it down with `docker compose down`.
```bash
docker build . --target run --tag acme-reviews:local
- docker run --rm -p 8750:8080 -e Database__SeedSampleData=true acme-reviews:local
+ docker run --rm -p 7350:8080 -e Database__SeedSampleData=true acme-reviews:local
```
@@ -30,12 +30,12 @@ The image is built on the [AWS Lambda .NET base image](https://gallery.ecr.aws/l
Then invoke it from another terminal:
```bash
-curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" -d '{"limit":5}'
+curl -X POST "http://localhost:7350/2015-03-31/functions/function/invocations" -d '{"limit":5}'
```
## See the telemetry
-With Docker Compose, the function sends its traces, metrics and logs over OTLP to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), which forwards them to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8752](http://localhost:8752). Each invocation is one trace: the function's span from the OpenTelemetry Lambda wrapper, with the use case under it. Traces and metrics are flushed before the function returns, so they're there by the time `curl` is. The logs still go to the console too, so `docker compose logs function` works as it always did. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+With Docker Compose, the function sends its traces, metrics and logs over OTLP to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), which forwards them to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7352](http://localhost:7352). Each invocation is one trace: the function's span from the OpenTelemetry Lambda wrapper, with the use case under it. Traces and metrics are flushed before the function returns, so they're there by the time `curl` is. The logs still go to the console too, so `docker compose logs function` works as it always did. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
Run the image on its own and nothing is exported, because `OTEL_EXPORTER_OTLP_ENDPOINT` isn't set. Set it to send the telemetry to a collector of your own.
@@ -44,10 +44,10 @@ Run the image on its own and nothing is exported, because `OTEL_EXPORTER_OTLP_EN
The image runs `GetMovies` by default. The same image also has `GetAuthors` and `GetReviews`. Pass the function's full handler name to `docker run` to start a different one.
```bash
-docker run --rm -p 8750:8080 -e Database__SeedSampleData=true acme-reviews:local \
+docker run --rm -p 7350:8080 -e Database__SeedSampleData=true acme-reviews:local \
"Presentation::WorksOnMyMachine.Reviews.Presentation.LambdaFunction.Handler_GetReviews_Generated::GetReviews"
-curl -X POST "http://localhost:8750/2015-03-31/functions/function/invocations" \
+curl -X POST "http://localhost:7350/2015-03-31/functions/function/invocations" \
-d '{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","limit":2}'
```
@@ -85,5 +85,5 @@ docker build . --target run --tag acme-reviews:latest \
```
diff --git a/src/content/docs/lambda/reference/configuration.mdx b/src/content/docs/lambda/reference/configuration.mdx
index 81cb5a6..84dace0 100644
--- a/src/content/docs/lambda/reference/configuration.mdx
+++ b/src/content/docs/lambda/reference/configuration.mdx
@@ -21,11 +21,11 @@ sidebar:
| Service | What it is | Port |
| --- | --- | --- |
-| `function` | The image, built from the `run` stage, behind the runtime interface emulator | 8750 (8080 inside the container) |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8753 (gRPC), 8754 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8752 |
+| `function` | The image, built from the `run` stage, behind the runtime interface emulator | 7350 (the emulator listens on 8080 inside the container) |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 7353 (gRPC), 7354 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 7352 |
-The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8750 to 8759 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 7350 to 7359 and runs beside a solution made from any other template: Minimal API 7310, MCP 7320, GraphQL 7330, Web API 7340 and Lambda Function 7350. Inside their containers the emulator, dashboard and collector keep their usual ports.
## Docker build arguments
diff --git a/src/content/docs/mcp/getting-started.mdx b/src/content/docs/mcp/getting-started.mdx
index 8c97a66..c1be889 100644
--- a/src/content/docs/mcp/getting-started.mdx
+++ b/src/content/docs/mcp/getting-started.mdx
@@ -55,12 +55,12 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8721`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:7321`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
5. Call a tool. The server speaks Streamable HTTP at `/mcp`, and because it's stateless you can send a single JSON-RPC request with nothing before it.
```bash
- curl -X POST "https://localhost:8721/mcp" \
+ curl -X POST "https://localhost:7321/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_search","arguments":{"query":"space","limit":2}}}'
```
@@ -86,7 +86,7 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
docker compose up -d --build
```
- Open [http://localhost:8725](http://localhost:8725), connect, and the **Tools**, **Resources** and **Prompts** tabs show everything the server offers, with a form for each.
+ Open [http://localhost:7325](http://localhost:7325), connect, and the **Tools**, **Resources** and **Prompts** tabs show everything the server offers, with a form for each.
7. Run the tests while you're here.
diff --git a/src/content/docs/mcp/guides/add-a-tool.mdx b/src/content/docs/mcp/guides/add-a-tool.mdx
index 7aa3037..89a89a7 100644
--- a/src/content/docs/mcp/guides/add-a-tool.mdx
+++ b/src/content/docs/mcp/guides/add-a-tool.mdx
@@ -89,7 +89,7 @@ This guide adds `authors_get`, which returns one author with every review they'v
```
```bash
- curl -X POST "https://localhost:8721/mcp" \
+ curl -X POST "https://localhost:7321/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"authors_get","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5"}}}'
```
diff --git a/src/content/docs/mcp/guides/connect-a-client.mdx b/src/content/docs/mcp/guides/connect-a-client.mdx
index 815abe5..4c05cd0 100644
--- a/src/content/docs/mcp/guides/connect-a-client.mdx
+++ b/src/content/docs/mcp/guides/connect-a-client.mdx
@@ -7,14 +7,14 @@ sidebar:
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
-The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with Docker Compose and give the client `http://localhost:8720/mcp`. With `dotnet run` the address is `https://localhost:8721/mcp`, which needs a client that trusts the .NET development certificate, so Compose is the easier one to connect to.
+The server speaks [Streamable HTTP](https://modelcontextprotocol.io/specification/latest/basic/transports), the current remote transport in the MCP spec, so any client that supports remote servers can connect to it. Start the server with Docker Compose and give the client `http://localhost:7320/mcp`. With `dotnet run` the address is `https://localhost:7321/mcp`, which needs a client that trusts the .NET development certificate, so Compose is the easier one to connect to.
No client to hand? Docker Compose can start one for you: see [Chat with it](../run-locally/#chat-with-it).
```bash
- claude mcp add --transport http acme-movies http://localhost:8720/mcp
+ claude mcp add --transport http acme-movies http://localhost:7320/mcp
```
Then ask it something like "which movie has the best reviews?" and watch it call `movies_search` and `movies_get` on its own.
@@ -28,7 +28,7 @@ No client to hand? Docker Compose can start one for you: see [Chat with it](../r
```json
{
"servers": {
- "acme-movies": { "type": "http", "url": "http://localhost:8720/mcp" }
+ "acme-movies": { "type": "http", "url": "http://localhost:7320/mcp" }
}
}
```
@@ -39,7 +39,7 @@ No client to hand? Docker Compose can start one for you: see [Chat with it](../r
```json
{
"mcpServers": {
- "acme-movies": { "url": "http://localhost:8720/mcp" }
+ "acme-movies": { "url": "http://localhost:7320/mcp" }
}
}
```
@@ -51,7 +51,7 @@ No client to hand? Docker Compose can start one for you: see [Chat with it](../r
using ModelContextProtocol.Client;
await using var client = await McpClient.CreateAsync(
- new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:8720/mcp") }));
+ new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("http://localhost:7320/mcp") }));
var result = await client.CallToolAsync("movies_search", new Dictionary { ["query"] = "space" });
```
diff --git a/src/content/docs/mcp/guides/database.mdx b/src/content/docs/mcp/guides/database.mdx
index e6dcc30..445f2e2 100644
--- a/src/content/docs/mcp/guides/database.mdx
+++ b/src/content/docs/mcp/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8720:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 7320:7320 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index e9afd69..fde58f7 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8721](https://localhost:8721), and on `https://localhost:8721`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once. The server is at `/mcp`.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:7321](https://localhost:7321), and on `https://localhost:7321`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once. The server is at `/mcp`.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts the MCP Inspector, an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the server on [http://localhost:8720/mcp](http://localhost:8720/mcp). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts the MCP Inspector, an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the server on [http://localhost:7320/mcp](http://localhost:7320/mcp). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8720:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 7320:7320 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -44,21 +44,21 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
| `/.well-known/oauth-protected-resource/mcp` | Protected resource metadata, only when [authentication is on](../secure-the-server/) |
-| [http://localhost:8725](http://localhost:8725) | The MCP Inspector, when running with Docker Compose |
-| [http://localhost:8726](http://localhost:8726) | Open WebUI, when running with Docker Compose and the `demo` profile |
-| [http://localhost:8722](http://localhost:8722) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:7325](http://localhost:7325) | The MCP Inspector, when running with Docker Compose |
+| [http://localhost:7326](http://localhost:7326) | Open WebUI, when running with Docker Compose and the `demo` profile |
+| [http://localhost:7322](http://localhost:7322) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## Use the Inspector
-The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the official test client, and Docker Compose runs it already pointed at `http://api:8080/mcp`. Open it, press **Connect**, and:
+The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the official test client, and Docker Compose runs it already pointed at `http://api:7320/mcp`. Open it, press **Connect**, and:
- **Tools** lists every tool with its description, annotations and input schema, and gives you a form to call it. A result lists its movies, authors and reviews under **Resource Links**, and expanding one reads it.
- **Resources** lets you read `movies://top-rated`, `server://version` or any `movies://`, `authors://` or `reviews://` URI, and the ids autocomplete as you type an id or a title.
- **Prompts** renders each prompt with its arguments, and the title, name and id arguments autocomplete from the database.
-Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `https://localhost:8721/mcp`. Node doesn't read the system's trusted certificates by default, so if it can't connect, use Docker Compose instead.
+Running with `dotnet run` instead? Start the Inspector yourself with `npx @modelcontextprotocol/inspector` and point it at `https://localhost:7321/mcp`. Node doesn't read the system's trusted certificates by default, so if it can't connect, use Docker Compose instead.
## Chat with it
@@ -68,36 +68,36 @@ Want to ask for things in plain English? The optional `demo` profile adds [Open
docker compose --profile demo up -d --build
```
-Open [http://localhost:8726](http://localhost:8726), dismiss the release notes and ask it to list the movies. It uses Kilo's free gateway as the model, so there's no account or key, but it only works online, and your prompts and tool results go to Kilo, a third party. Keep it to sample data. Solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. Stop it with `docker compose --profile demo down`.
+Open [http://localhost:7326](http://localhost:7326), dismiss the release notes and ask it to list the movies. It uses Kilo's free gateway as the model, so there's no account or key, but it only works online, and your prompts and tool results go to Kilo, a third party. Keep it to sample data. Solutions created with `--include-auth` leave it out, because Open WebUI has no token to send. Stop it with `docker compose --profile demo down`.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the server, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8722](http://localhost:8722). Open **Traces** to follow a request from the HTTP request through the tool call to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the server, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7322](http://localhost:7322). Open **Traces** to follow a request from the HTTP request through the tool call to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The server only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the server doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the server at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8723 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:7323 dotnet run --project ./src/Presentation
```
## Try the tools from the terminal
-The server is stateless, so a single `tools/call` works without an `initialize` first. These use the `dotnet run` address; with Docker Compose, use `http://localhost:8720/mcp` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
+The server is stateless, so a single `tools/call` works without an `initialize` first. These use the `dotnet run` address; with Docker Compose, use `http://localhost:7320/mcp` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [tool reference](../../reference/tools/) for every argument and result.
```bash
# One movie, with every review
-curl -X POST "https://localhost:8721/mcp" \
+curl -X POST "https://localhost:7321/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"movies_get","arguments":{"movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28"}}}'
# Write a review
-curl -X POST "https://localhost:8721/mcp" \
+curl -X POST "https://localhost:7321/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reviews_write","arguments":{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}}}'
# Read a resource
-curl -X POST "https://localhost:8721/mcp" \
+curl -X POST "https://localhost:7321/mcp" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"server://version"}}'
```
@@ -136,5 +136,5 @@ docker build . --target run --tag acme-movies:latest \
The `server://version` resource and the `serverInfo` in the initialize response then report `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/mcp/guides/secure-the-server.mdx b/src/content/docs/mcp/guides/secure-the-server.mdx
index 157222d..2997a2f 100644
--- a/src/content/docs/mcp/guides/secure-the-server.mdx
+++ b/src/content/docs/mcp/guides/secure-the-server.mdx
@@ -42,7 +42,7 @@ Out of the box the server runs wide open, and there's no authentication code in
3. Get a token from the authority for that audience, and send it.
```bash
- curl -X POST "https://localhost:8721/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
+ curl -X POST "https://localhost:7321/mcp" -H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
diff --git a/src/content/docs/mcp/guides/ship-the-container.mdx b/src/content/docs/mcp/guides/ship-the-container.mdx
index b46623a..83c5187 100644
--- a/src/content/docs/mcp/guides/ship-the-container.mdx
+++ b/src/content/docs/mcp/guides/ship-the-container.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Aside } from '@astrojs/starlight/components';
-The `run` stage of the `Dockerfile` is the whole server: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 8080 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
+The `run` stage of the `Dockerfile` is the whole server: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 7320 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
## Build it
@@ -59,8 +59,8 @@ In a solution created with `--include-auth`, set `Authentication__Authority` and
## Leave the Inspector at home
-`docker-compose.yml` starts the MCP Inspector next to the server because it's handy on a laptop. It's a development tool, not part of the server, so don't ship it. In production, the only thing listening is the server on port 8080.
+`docker-compose.yml` starts the MCP Inspector next to the server because it's handy on a laptop. It's a development tool, not part of the server, so don't ship it. In production, the only thing listening is the server on port 7320.
diff --git a/src/content/docs/mcp/index.mdx b/src/content/docs/mcp/index.mdx
index 02615ab..42272f4 100644
--- a/src/content/docs/mcp/index.mdx
+++ b/src/content/docs/mcp/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open the MCP Inspector at [http://localhost:8725](http://localhost:8725). It's already pointed at the server on [http://localhost:8720/mcp](http://localhost:8720/mcp), so connect, open **Tools** and start clicking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8722](http://localhost:8722).
+Then open the MCP Inspector at [http://localhost:7325](http://localhost:7325). It's already pointed at the server on [http://localhost:7320/mcp](http://localhost:7320/mcp), so connect, open **Tools** and start clicking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7322](http://localhost:7322).
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and `/mcp` needs a bearer token, with protected resource metadata so MCP clients can find your authorization server and sign in. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/mcp/reference/configuration.mdx b/src/content/docs/mcp/reference/configuration.mdx
index 2ffda63..42861af 100644
--- a/src/content/docs/mcp/reference/configuration.mdx
+++ b/src/content/docs/mcp/reference/configuration.mdx
@@ -12,7 +12,7 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Variable | Default | Description |
| --- | --- | --- |
| `DOTNET_ENVIRONMENT` | none | Layers `appsettings..json` over `appsettings.json`. The template ships `local` and `docker` |
-| `ASPNETCORE_URLS` | `http://+:8080` in the container | The addresses to listen on |
+| `ASPNETCORE_HTTP_PORTS` | `7320` in the container | The port to listen on |
| `ASPNETCORE_FORWARDEDHEADERS_ENABLED` | none | Set to `true` behind a proxy that ends TLS, so HTTPS redirection sees the original scheme |
| `ConnectionStrings__MovieReviews` | `Data Source=movie-reviews.db` | The SQLite connection string. `appsettings.docker.json` points it at `/app/data/movie-reviews.db` |
| `Database__SeedSampleData` | `false` | When `true`, seeds sample data into an empty database. On in `local` and `docker` |
@@ -35,12 +35,12 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8720 (8080 inside the container) |
-| `inspector` | The MCP Inspector, pointed at the server | 8725, on localhost only |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8723 (gRPC), 8724 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8722 |
+| `api` | The image, built from the `run` stage | 7320, inside the container too |
+| `inspector` | The MCP Inspector, pointed at the server | 7325, on localhost only |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 7323 (gRPC), 7324 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 7322 |
-The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8720 to 8729 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports, except the Inspector, which only accepts calls from the origin it listens on and so uses 8725 inside too.
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 7320 to 7329 and runs beside a solution made from any other template: Minimal API 7310, MCP 7320, GraphQL 7330, Web API 7340 and Lambda Function 7350. The app and the Inspector use the same ports inside their containers. The dashboard and collector keep their usual ones.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -59,4 +59,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8720` and `https://localhost:8721`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:7320` and `https://localhost:7321`.
diff --git a/src/content/docs/minimalapi/getting-started.mdx b/src/content/docs/minimalapi/getting-started.mdx
index 3d6dbe9..e643eae 100644
--- a/src/content/docs/minimalapi/getting-started.mdx
+++ b/src/content/docs/minimalapi/getting-started.mdx
@@ -55,14 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8711`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:7311`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
-5. Open the Scalar API reference at [https://localhost:8711/scalar](https://localhost:8711/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+5. Open the Scalar API reference at [https://localhost:7311/scalar](https://localhost:7311/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "https://localhost:8711/api/movies?query=space&limit=2"
+ curl "https://localhost:7311/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
index 268a48c..3cb7986 100644
--- a/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/minimalapi/guides/add-an-endpoint.mdx
@@ -101,7 +101,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "https://localhost:8711/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:7311/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/minimalapi/guides/database.mdx b/src/content/docs/minimalapi/guides/database.mdx
index a065734..0edef7b 100644
--- a/src/content/docs/minimalapi/guides/database.mdx
+++ b/src/content/docs/minimalapi/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8710:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 7310:7310 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 414dabd..862e704 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8711](https://localhost:8711), and on `https://localhost:8711`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:7311](https://localhost:7311), and on `https://localhost:7311`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8710](http://localhost:8710). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:7310](http://localhost:7310). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8710:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 7310:7310 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -45,34 +45,34 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
| `/api/version` | The version stamped into the build. Needs a token when authentication is on |
-| [http://localhost:8712](http://localhost:8712) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:7312](http://localhost:7312) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8712](http://localhost:8712). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7312](http://localhost:7312). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8713 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:7313 dotnet run --project ./src/Presentation
```
## Try the endpoints
-These use the `dotnet run` address; with Docker Compose, use `http://localhost:8710` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:7310` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "https://localhost:8711/api/movies?query=space&limit=2"
+curl "https://localhost:7311/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "https://localhost:8711/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:7311/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "https://localhost:8711/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:7311/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/minimalapi/guides/secure-the-api.mdx b/src/content/docs/minimalapi/guides/secure-the-api.mdx
index dffaa61..6c56f2d 100644
--- a/src/content/docs/minimalapi/guides/secure-the-api.mdx
+++ b/src/content/docs/minimalapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "https://localhost:8711/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:7311/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/minimalapi/guides/ship-the-container.mdx b/src/content/docs/minimalapi/guides/ship-the-container.mdx
index f80f264..3ab936e 100644
--- a/src/content/docs/minimalapi/guides/ship-the-container.mdx
+++ b/src/content/docs/minimalapi/guides/ship-the-container.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Aside } from '@astrojs/starlight/components';
-The `run` stage of the `Dockerfile` is the whole API: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 8080 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
+The `run` stage of the `Dockerfile` is the whole API: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 7310 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
## Build it
@@ -58,5 +58,5 @@ The SQLite file lives in `/app/data`. Mount a volume there or, better, [point In
In a solution created with `--include-auth`, set `Authentication__Authority` and `Authentication__Audience` as environment variables to point each environment at its own authorization server. Without `--include-auth` there's nothing to turn on yet. The [security guide](../secure-the-api/) has the details.
diff --git a/src/content/docs/minimalapi/index.mdx b/src/content/docs/minimalapi/index.mdx
index ab1bbd6..1f46b6f 100644
--- a/src/content/docs/minimalapi/index.mdx
+++ b/src/content/docs/minimalapi/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open the Scalar API reference at [http://localhost:8710/scalar](http://localhost:8710/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8712](http://localhost:8712), because `Console.WriteLine` is not an observability strategy.
+Then open the Scalar API reference at [http://localhost:7310/scalar](http://localhost:7310/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7312](http://localhost:7312), because `Console.WriteLine` is not an observability strategy.
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and every movie, author and review endpoint needs a JWT bearer token. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/minimalapi/reference/configuration.mdx b/src/content/docs/minimalapi/reference/configuration.mdx
index a105192..87d85d0 100644
--- a/src/content/docs/minimalapi/reference/configuration.mdx
+++ b/src/content/docs/minimalapi/reference/configuration.mdx
@@ -12,7 +12,7 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Variable | Default | Description |
| --- | --- | --- |
| `DOTNET_ENVIRONMENT` | none | Layers `appsettings..json` over `appsettings.json`. The template ships `local` and `docker` |
-| `ASPNETCORE_URLS` | `http://+:8080` in the container | The addresses to listen on |
+| `ASPNETCORE_HTTP_PORTS` | `7310` in the container | The port to listen on |
| `ASPNETCORE_FORWARDEDHEADERS_ENABLED` | none | Set to `true` behind a proxy that ends TLS, so HTTPS redirection sees the original scheme |
| `ConnectionStrings__MovieReviews` | `Data Source=movie-reviews.db` | The SQLite connection string. `appsettings.docker.json` points it at `/app/data/movie-reviews.db` |
| `Database__SeedSampleData` | `false` | When `true`, seeds sample data into an empty database. On in `local` and `docker` |
@@ -34,11 +34,11 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8710 (8080 inside the container) |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8713 (gRPC), 8714 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8712 |
+| `api` | The image, built from the `run` stage | 7310, inside the container too |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 7313 (gRPC), 7314 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 7312 |
-The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8710 to 8719 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 7310 to 7319 and runs beside a solution made from any other template: Minimal API 7310, MCP 7320, GraphQL 7330, Web API 7340 and Lambda Function 7350. The app uses the same port inside its container; the dashboard and collector keep their usual ones.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -57,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8710` and `https://localhost:8711`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:7310` and `https://localhost:7311`.
diff --git a/src/content/docs/minimalapi/reference/endpoints.mdx b/src/content/docs/minimalapi/reference/endpoints.mdx
index cf61b6d..305658a 100644
--- a/src/content/docs/minimalapi/reference/endpoints.mdx
+++ b/src/content/docs/minimalapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Endpoints`, one static class per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8711`; with Docker Compose it's `http://localhost:8710`.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:7311`; with Docker Compose it's `http://localhost:7310`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8711/api/movies?query=space&limit=2"
+curl "https://localhost:7311/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8711/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:7311/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "https://localhost:8711/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab
```
```bash
-curl "https://localhost:8711/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:7311/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "https://localhost:8711/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:7311/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
diff --git a/src/content/docs/webapi/getting-started.mdx b/src/content/docs/webapi/getting-started.mdx
index 6d71600..d1787aa 100644
--- a/src/content/docs/webapi/getting-started.mdx
+++ b/src/content/docs/webapi/getting-started.mdx
@@ -55,14 +55,14 @@ You need the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0). [
dotnet run --project ./src/Presentation
```
- It answers on `https://localhost:8741`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ It answers on `https://localhost:7341`. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
-5. Open the Scalar API reference at [https://localhost:8741/scalar](https://localhost:8741/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
+5. Open the Scalar API reference at [https://localhost:7341/scalar](https://localhost:7341/scalar). Every endpoint is there with its parameters, responses and a **Send** button, so you never have to remember the shape of a request again.
6. Or call it from the terminal.
```bash
- curl "https://localhost:8741/api/movies?query=space&limit=2"
+ curl "https://localhost:7341/api/movies?query=space&limit=2"
```
You get a page of movies back:
diff --git a/src/content/docs/webapi/guides/add-an-endpoint.mdx b/src/content/docs/webapi/guides/add-an-endpoint.mdx
index 3704c54..0c23e89 100644
--- a/src/content/docs/webapi/guides/add-an-endpoint.mdx
+++ b/src/content/docs/webapi/guides/add-an-endpoint.mdx
@@ -95,7 +95,7 @@ This guide adds `GET /api/movies/{id}/reviews`, one page of the reviews written
```bash
dotnet test
dotnet run --project ./src/Presentation
- curl "https://localhost:8741/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
+ curl "https://localhost:7341/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28/reviews?limit=2"
```
```json
diff --git a/src/content/docs/webapi/guides/database.mdx b/src/content/docs/webapi/guides/database.mdx
index c86b948..dc4aed6 100644
--- a/src/content/docs/webapi/guides/database.mdx
+++ b/src/content/docs/webapi/guides/database.mdx
@@ -30,7 +30,7 @@ The next start builds a fresh database and seeds it again.
Set `ConnectionStrings__MovieReviews`. To keep everything in memory, which is what the tests do:
```bash
-docker run --rm -p 8740:8080 -e DOTNET_ENVIRONMENT=docker \
+docker run --rm -p 7340:7340 -e DOTNET_ENVIRONMENT=docker \
-e ConnectionStrings__MovieReviews="Data Source=MovieReviews;Mode=Memory;Cache=Shared" \
acme-movies:local
```
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index a98e60e..8aceb54 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -17,19 +17,19 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
dotnet run --project ./src/Presentation
```
- `launchSettings.json` sets the environment to `local` and listens on [https://localhost:8741](https://localhost:8741), and on `https://localhost:8741`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
+ `launchSettings.json` sets the environment to `local` and listens on [https://localhost:7341](https://localhost:7341), and on `https://localhost:7341`, which redirects to it. If your browser or `curl` doesn't trust the .NET development certificate yet, run `dotnet dev-certs https --trust` once.
```bash
docker compose up -d --build
```
- Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:8740](http://localhost:8740). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
+ Compose builds the `run` stage, starts an OpenTelemetry Collector and the Aspire dashboard next to it, and publishes the API on [http://localhost:7340](http://localhost:7340). Shut it down with `docker compose down`, or `docker compose down -v` to drop the database too.
```bash
docker build . --target run --tag acme-movies:local
- docker run --rm -p 8740:8080 -e DOTNET_ENVIRONMENT=docker acme-movies:local
+ docker run --rm -p 7340:7340 -e DOTNET_ENVIRONMENT=docker acme-movies:local
```
Without Compose there's no collector, so the logs stay on the console and nothing else is exported. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to send them to a collector of your own.
@@ -45,34 +45,34 @@ There are two ready-made environments. `local` is for `dotnet run`: sample data
| `/health/live` | Answers 200 while the process is up |
| `/health/ready` | Answers 200 when the database can be reached |
| `/api/version` | The version stamped into the build. Needs a token when authentication is on |
-| [http://localhost:8742](http://localhost:8742) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
+| [http://localhost:7342](http://localhost:7342) | The Aspire dashboard, with the logs, traces and metrics, when running with Docker Compose |
The health endpoints answer without a token even when authentication is on, so a load balancer never needs to log in.
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8742](http://localhost:8742). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7342](http://localhost:7342). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
```bash
docker compose up -d otel-collector
-OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8743 dotnet run --project ./src/Presentation
+OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:7343 dotnet run --project ./src/Presentation
```
## Try the endpoints
-These use the `dotnet run` address; with Docker Compose, use `http://localhost:8740` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
+These use the `dotnet run` address; with Docker Compose, use `http://localhost:7340` instead. The sample data uses a fixed seed, so these ids are the same on every machine. See the [endpoint reference](../../reference/endpoints/) for every parameter and response.
```bash
# Search movies, two at a time
-curl "https://localhost:8741/api/movies?query=space&limit=2"
+curl "https://localhost:7341/api/movies?query=space&limit=2"
# One movie, with its reviews
-curl "https://localhost:8741/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
+curl "https://localhost:7341/api/movies/cb188ef6-14e7-f85f-2f78-924c2ab26b28"
# Write a review
-curl -X POST "https://localhost:8741/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:7341/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
@@ -110,5 +110,5 @@ docker build . --target run --tag acme-movies:latest \
`GET /api/version` then reports `1.2.3-beta` and the commit it was built from.
diff --git a/src/content/docs/webapi/guides/secure-the-api.mdx b/src/content/docs/webapi/guides/secure-the-api.mdx
index fc800b7..34d4f3c 100644
--- a/src/content/docs/webapi/guides/secure-the-api.mdx
+++ b/src/content/docs/webapi/guides/secure-the-api.mdx
@@ -30,7 +30,7 @@ Out of the box the API runs wide open, and there's no authentication code in it
2. Get a token from the authority for that audience, and send it.
```bash
- curl "https://localhost:8741/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
+ curl "https://localhost:7341/api/movies" -H "Authorization: Bearer eyJhbGciOi..."
```
Without a token, or with one from the wrong issuer or for the wrong audience, the API answers `401 Unauthorized`.
diff --git a/src/content/docs/webapi/guides/ship-the-container.mdx b/src/content/docs/webapi/guides/ship-the-container.mdx
index f80f264..f06e1fc 100644
--- a/src/content/docs/webapi/guides/ship-the-container.mdx
+++ b/src/content/docs/webapi/guides/ship-the-container.mdx
@@ -7,7 +7,7 @@ sidebar:
import { Aside } from '@astrojs/starlight/components';
-The `run` stage of the `Dockerfile` is the whole API: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 8080 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
+The `run` stage of the `Dockerfile` is the whole API: one image, built on `mcr.microsoft.com/dotnet/aspnet:10.0-alpine`, that listens on port 7340 and runs as the image's non-root `app` user. It runs anywhere a container runs, which these days is everywhere, including a surprising number of fridges.
## Build it
@@ -58,5 +58,5 @@ The SQLite file lives in `/app/data`. Mount a volume there or, better, [point In
In a solution created with `--include-auth`, set `Authentication__Authority` and `Authentication__Audience` as environment variables to point each environment at its own authorization server. Without `--include-auth` there's nothing to turn on yet. The [security guide](../secure-the-api/) has the details.
diff --git a/src/content/docs/webapi/index.mdx b/src/content/docs/webapi/index.mdx
index 053593f..53d9a07 100644
--- a/src/content/docs/webapi/index.mdx
+++ b/src/content/docs/webapi/index.mdx
@@ -44,7 +44,7 @@ cd WorksOnMyMachine.Movies
docker compose up -d
```
-Then open the Scalar API reference at [http://localhost:8740/scalar](http://localhost:8740/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:8742](http://localhost:8742), because `Console.WriteLine` is not an observability strategy.
+Then open the Scalar API reference at [http://localhost:7340/scalar](http://localhost:7340/scalar) and start poking. The logs, traces and metrics go to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7342](http://localhost:7342), because `Console.WriteLine` is not an observability strategy.
Want it locked down? Add `--include-auth`, `--authority` and `--audience` when you create the solution and every movie, author and review endpoint needs a JWT bearer token. Leave `--include-auth` off and there's no auth code at all, which is lovely on a laptop and less lovely in production.
diff --git a/src/content/docs/webapi/reference/configuration.mdx b/src/content/docs/webapi/reference/configuration.mdx
index ce4be2b..4867295 100644
--- a/src/content/docs/webapi/reference/configuration.mdx
+++ b/src/content/docs/webapi/reference/configuration.mdx
@@ -12,7 +12,7 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Variable | Default | Description |
| --- | --- | --- |
| `DOTNET_ENVIRONMENT` | none | Layers `appsettings..json` over `appsettings.json`. The template ships `local` and `docker` |
-| `ASPNETCORE_URLS` | `http://+:8080` in the container | The addresses to listen on |
+| `ASPNETCORE_HTTP_PORTS` | `7340` in the container | The port to listen on |
| `ASPNETCORE_FORWARDEDHEADERS_ENABLED` | none | Set to `true` behind a proxy that ends TLS, so HTTPS redirection sees the original scheme |
| `ConnectionStrings__MovieReviews` | `Data Source=movie-reviews.db` | The SQLite connection string. `appsettings.docker.json` points it at `/app/data/movie-reviews.db` |
| `Database__SeedSampleData` | `false` | When `true`, seeds sample data into an empty database. On in `local` and `docker` |
@@ -34,11 +34,11 @@ Every setting in `appsettings.json` can be overridden with an environment variab
| Service | What it is | Port |
| --- | --- | --- |
-| `api` | The image, built from the `run` stage | 8740 (8080 inside the container) |
-| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 8743 (gRPC), 8744 (HTTP) |
-| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 8742 |
+| `api` | The image, built from the `run` stage | 7340, inside the container too |
+| `otel-collector` | The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), configured by `otel-collector.yaml` | 7343 (gRPC), 7344 (HTTP) |
+| `aspire-dashboard` | The [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone), where the collector sends everything | 7342 |
-The ports are the ones on your machine. Each template has its own block of ten, so this one uses 8740 to 8749 and runs beside a solution made from any other template: Minimal API 8710, MCP 8720, GraphQL 8730, Web API 8740 and Lambda Function 8750. Inside their containers the services keep their usual ports.
+The ports are the ones on your machine. Each template has its own block of ten, so this one uses 7340 to 7349 and runs beside a solution made from any other template: Minimal API 7310, MCP 7320, GraphQL 7330, Web API 7340 and Lambda Function 7350. The app uses the same port inside its container; the dashboard and collector keep their usual ones.
The `api` service runs with `DOTNET_ENVIRONMENT=docker`, mounts the `movie-reviews-data` volume at `/app/data`, and sends its logs, traces and metrics over OTLP to `otel-collector`, which forwards them to `aspire-dashboard`.
@@ -57,4 +57,4 @@ Every span, metric and log also carries `service.version`, the version stamped i
## Ports when running with dotnet run
-`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:8740` and `https://localhost:8741`.
+`src/Presentation/Properties/launchSettings.json` sets `DOTNET_ENVIRONMENT=local` and listens on `http://localhost:7340` and `https://localhost:7341`.
diff --git a/src/content/docs/webapi/reference/endpoints.mdx b/src/content/docs/webapi/reference/endpoints.mdx
index 528be2d..c5a2240 100644
--- a/src/content/docs/webapi/reference/endpoints.mdx
+++ b/src/content/docs/webapi/reference/endpoints.mdx
@@ -7,7 +7,7 @@ sidebar:
Every endpoint lives in `src/Presentation/Controllers`, one controller per feature. Requests and responses are JSON with camelCase property names, read case-insensitively. Properties with no value are left out of responses. In a solution created with `--include-auth`, every endpoint except health needs a token (see [Secure the API](../../guides/secure-the-api/)).
-The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:8741`; with Docker Compose it's `http://localhost:8740`.
+The OpenAPI document at `/openapi/v1.json` describes all of this too, and Scalar at `/scalar` lets you call it. The examples use the `dotnet run` address, `https://localhost:7341`; with Docker Compose it's `http://localhost:7340`.
| Method | Route | Returns |
| --- | --- | --- |
@@ -35,7 +35,7 @@ Lists movies ordered by title, one page at a time, optionally only those whose t
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8741/api/movies?query=space&limit=2"
+curl "https://localhost:7341/api/movies?query=space&limit=2"
```
```json
@@ -92,7 +92,7 @@ Lists reviews ordered by movie then author, one page at a time. Filter by movie,
| `limit` | integer | `10` | 1 to 50 |
```bash
-curl "https://localhost:8741/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
+curl "https://localhost:7341/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab26b28&limit=2"
```
```json
@@ -108,7 +108,7 @@ curl "https://localhost:8741/api/reviews?movieId=cb188ef6-14e7-f85f-2f78-924c2ab
```
```bash
-curl "https://localhost:8741/api/reviews?query=funny&minStars=4&limit=2"
+curl "https://localhost:7341/api/reviews?query=funny&minStars=4&limit=2"
```
```json
@@ -139,7 +139,7 @@ Creates a review. The author and the movie must already exist.
| `text` | string | Optional. Up to 1000 characters |
```bash
-curl -X POST "https://localhost:8741/api/reviews" -H "Content-Type: application/json" \
+curl -X POST "https://localhost:7341/api/reviews" -H "Content-Type: application/json" \
-d '{"authorId":"01fa8b58-05d2-abd4-e55c-2633222dd0a5","movieId":"cb188ef6-14e7-f85f-2f78-924c2ab26b28","stars":5,"text":"Great film. I cried at the ending."}'
```
From e34c680178aae6d3277f65bca0ea85d6347ef906 Mon Sep 17 00:00:00 2001
From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com>
Date: Fri, 6 Nov 2026 23:58:03 +0000
Subject: [PATCH 6/6] Name the collector's own ports in the run-locally guides
---
src/content/docs/graphql/guides/run-locally.mdx | 2 +-
src/content/docs/lambda/guides/run-locally.mdx | 2 +-
src/content/docs/mcp/guides/run-locally.mdx | 2 +-
src/content/docs/minimalapi/guides/run-locally.mdx | 2 +-
src/content/docs/webapi/guides/run-locally.mdx | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/content/docs/graphql/guides/run-locally.mdx b/src/content/docs/graphql/guides/run-locally.mdx
index 939f2b1..fa51c50 100644
--- a/src/content/docs/graphql/guides/run-locally.mdx
+++ b/src/content/docs/graphql/guides/run-locally.mdx
@@ -50,7 +50,7 @@ The health endpoints answer without a token even when authentication is on, so a
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7332](http://localhost:7332). Open **Traces** to follow a request from the HTTP request through the GraphQL operation to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7332](http://localhost:7332). Open **Traces** to follow a request from the HTTP request through the GraphQL operation to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 7333 and 7334. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
diff --git a/src/content/docs/lambda/guides/run-locally.mdx b/src/content/docs/lambda/guides/run-locally.mdx
index 55a6b5f..da3db0f 100644
--- a/src/content/docs/lambda/guides/run-locally.mdx
+++ b/src/content/docs/lambda/guides/run-locally.mdx
@@ -35,7 +35,7 @@ curl -X POST "http://localhost:7350/2015-03-31/functions/function/invocations" -
## See the telemetry
-With Docker Compose, the function sends its traces, metrics and logs over OTLP to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), which forwards them to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7352](http://localhost:7352). Each invocation is one trace: the function's span from the OpenTelemetry Lambda wrapper, with the use case under it. Traces and metrics are flushed before the function returns, so they're there by the time `curl` is. The logs still go to the console too, so `docker compose logs function` works as it always did. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+With Docker Compose, the function sends its traces, metrics and logs over OTLP to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/), which forwards them to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7352](http://localhost:7352). Each invocation is one trace: the function's span from the OpenTelemetry Lambda wrapper, with the use case under it. Traces and metrics are flushed before the function returns, so they're there by the time `curl` is. The logs still go to the console too, so `docker compose logs function` works as it always did. The dashboard has no login, and the collector accepts OTLP from anyone on ports 7353 and 7354. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
Run the image on its own and nothing is exported, because `OTEL_EXPORTER_OTLP_ENDPOINT` isn't set. Set it to send the telemetry to a collector of your own.
diff --git a/src/content/docs/mcp/guides/run-locally.mdx b/src/content/docs/mcp/guides/run-locally.mdx
index fde58f7..2e64fc8 100644
--- a/src/content/docs/mcp/guides/run-locally.mdx
+++ b/src/content/docs/mcp/guides/run-locally.mdx
@@ -72,7 +72,7 @@ Open [http://localhost:7326](http://localhost:7326), dismiss the release notes a
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the server, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7322](http://localhost:7322). Open **Traces** to follow a request from the HTTP request through the tool call to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the server, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7322](http://localhost:7322). Open **Traces** to follow a request from the HTTP request through the tool call to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 7323 and 7324. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The server only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the server doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the server at it:
diff --git a/src/content/docs/minimalapi/guides/run-locally.mdx b/src/content/docs/minimalapi/guides/run-locally.mdx
index 862e704..8502fe4 100644
--- a/src/content/docs/minimalapi/guides/run-locally.mdx
+++ b/src/content/docs/minimalapi/guides/run-locally.mdx
@@ -51,7 +51,7 @@ The health endpoints answer without a token even when authentication is on, so a
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7312](http://localhost:7312). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7312](http://localhost:7312). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 7313 and 7314. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it:
diff --git a/src/content/docs/webapi/guides/run-locally.mdx b/src/content/docs/webapi/guides/run-locally.mdx
index 8aceb54..d3454f2 100644
--- a/src/content/docs/webapi/guides/run-locally.mdx
+++ b/src/content/docs/webapi/guides/run-locally.mdx
@@ -51,7 +51,7 @@ The health endpoints answer without a token even when authentication is on, so a
## See the telemetry
-Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7342](http://localhost:7342). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 4317 and 4318. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
+Docker Compose runs an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) beside the API, and the collector forwards everything to the [Aspire dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) at [http://localhost:7342](http://localhost:7342). Open **Traces** to follow a request from the HTTP request to the use case, **Structured** for the logs, each linked to its trace, and **Metrics** for request durations, EF Core queries and the runtime. The dashboard keeps everything in memory, so it's empty again after a restart. The dashboard has no login, and the collector accepts OTLP from anyone on ports 7343 and 7344. That's on purpose, so there's nothing to sign in to on your machine, and it's why the compose file is for your machine only. Don't run it anywhere other people can reach.
The API only ever talks OTLP to the collector, and `otel-collector.yaml` decides where things go. To send them to a real backend, add its exporter there; the API doesn't change. To see a `dotnet run` in the dashboard, start the collector and point the API at it: