Skip to content

Commit e7b6e37

Browse files
Merge pull request #1 from dotnet-guide-com/feature/aspire-orchestration-sample
Add lightweight Aspire orchestration sample
2 parents 2ff4060 + 0199ab5 commit e7b6e37

10 files changed

Lines changed: 324 additions & 0 deletions

File tree

‎.github/workflows/build-samples.yml‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ on:
66
paths:
77
- "dotnet-ai/provider-agnostic-chat-gateway/**"
88
- "dotnet-ai/hybrid-search-ef-core-pgvector/**"
9+
- "dotnet-aspire/orchestrate-distributed-system/**"
910
- ".github/workflows/build-samples.yml"
1011

1112
pull_request:
1213
paths:
1314
- "dotnet-ai/provider-agnostic-chat-gateway/**"
1415
- "dotnet-ai/hybrid-search-ef-core-pgvector/**"
16+
- "dotnet-aspire/orchestrate-distributed-system/**"
1517
- ".github/workflows/build-samples.yml"
1618

1719
workflow_dispatch:
@@ -76,3 +78,28 @@ jobs:
7678
--project dotnet-ai/hybrid-search-ef-core-pgvector/HybridSearchMinimal.csproj \
7779
--configuration Release \
7880
--no-build
81+
82+
build-aspire-orchestration:
83+
name: Build Aspire orchestration sample
84+
runs-on: ubuntu-latest
85+
86+
steps:
87+
- name: Check out repository
88+
uses: actions/checkout@v5
89+
90+
- name: Install .NET 10 SDK
91+
uses: actions/setup-dotnet@v5
92+
with:
93+
dotnet-version: "10.0.x"
94+
95+
- name: Restore
96+
run: |
97+
dotnet restore \
98+
dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx
99+
100+
- name: Build
101+
run: |
102+
dotnet build \
103+
dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx \
104+
--configuration Release \
105+
--no-restore

‎README.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Each sample folder contains a focused implementation of one tutorial topic. The
1111
| --- | --- | --- |
1212
| [`dotnet-ai/provider-agnostic-chat-gateway`](dotnet-ai/provider-agnostic-chat-gateway/) | One HTTP chat endpoint using Ollama and optional OpenAI providers through `Microsoft.Extensions.AI.IChatClient` | [Build a switchable multi-provider AI gateway with IChatClient](https://www.dotnet-guide.com/tutorials/dotnet-ai/provider-agnostic-chat-gateway/) |
1313
| [`dotnet-ai/hybrid-search-ef-core-pgvector`](dotnet-ai/hybrid-search-ef-core-pgvector/) | Minimal Reciprocal Rank Fusion demo combining pre-ranked keyword and vector results | [Hybrid Search in .NET with EF Core 10 and pgvector](https://www.dotnet-guide.com/tutorials/dotnet-ai/hybrid-search-ef-core-pgvector/) |
14+
| [`dotnet-aspire/orchestrate-distributed-system`](dotnet-aspire/orchestrate-distributed-system/) | Minimal Aspire AppHost coordinating a web project and API with service discovery and startup ordering | [Aspire in .NET: Orchestrate, Run, and Deploy a Distributed System from One App Host](https://www.dotnet-guide.com/tutorials/dotnet-aspire/orchestrate-distributed-system/) |
1415

1516
## Companion articles
1617

@@ -54,6 +55,19 @@ tutorials/
5455
| |-- HybridSearchMinimal.csproj
5556
| |-- Program.cs
5657
| `-- README.md
58+
|-- dotnet-aspire/
59+
| `-- orchestrate-distributed-system/
60+
| |-- AspireOrchestrationMinimal.slnx
61+
| |-- README.md
62+
| |-- AspireOrchestrationMinimal.AppHost/
63+
| | |-- AppHost.cs
64+
| | `-- AspireOrchestrationMinimal.AppHost.csproj
65+
| |-- AspireOrchestrationMinimal.Api/
66+
| | |-- Program.cs
67+
| | `-- AspireOrchestrationMinimal.Api.csproj
68+
| `-- AspireOrchestrationMinimal.Web/
69+
| |-- Program.cs
70+
| `-- AspireOrchestrationMinimal.Web.csproj
5771
|-- .github/
5872
| `-- workflows/
5973
| `-- build-samples.yml
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var builder = WebApplication.CreateBuilder(args);
2+
3+
var app = builder.Build();
4+
5+
app.MapGet("/", () => "AspireOrchestrationMinimal.Api is running.");
6+
7+
app.MapGet("/message", () => Results.Ok(new
8+
{
9+
message = "Hello from the Aspire-managed API",
10+
service = "api"
11+
}));
12+
13+
app.Run();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var builder = DistributedApplication.CreateBuilder(args);
2+
3+
var api = builder
4+
.AddProject<Projects.AspireOrchestrationMinimal_Api>("api");
5+
6+
builder
7+
.AddProject<Projects.AspireOrchestrationMinimal_Web>("web")
8+
.WithReference(api)
9+
.WaitFor(api)
10+
.WithExternalHttpEndpoints();
11+
12+
builder.Build().Run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.4.6" />
12+
<ProjectReference Include="..\AspireOrchestrationMinimal.Api\AspireOrchestrationMinimal.Api.csproj" IsAspireProjectResource="true" />
13+
<ProjectReference Include="..\AspireOrchestrationMinimal.Web\AspireOrchestrationMinimal.Web.csproj" IsAspireProjectResource="true" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="10.8.0" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Microsoft.Extensions.ServiceDiscovery;
2+
3+
var builder = WebApplication.CreateBuilder(args);
4+
5+
builder.Services.AddServiceDiscovery();
6+
7+
builder.Services.AddHttpClient("api", client =>
8+
{
9+
client.BaseAddress = new Uri("https+http://api");
10+
})
11+
.AddServiceDiscovery();
12+
13+
var app = builder.Build();
14+
15+
app.MapGet("/", async (IHttpClientFactory httpClientFactory) =>
16+
{
17+
var client = httpClientFactory.CreateClient("api");
18+
19+
string apiMessage;
20+
21+
try
22+
{
23+
apiMessage = await client.GetStringAsync("/message");
24+
}
25+
catch (Exception ex)
26+
{
27+
apiMessage = $"API unavailable: {ex.Message}";
28+
}
29+
30+
var html = $"""
31+
<!DOCTYPE html>
32+
<html>
33+
<head>
34+
<meta charset="utf-8" />
35+
<title>Aspire orchestration sample</title>
36+
</head>
37+
<body>
38+
<h1>Aspire orchestration sample</h1>
39+
<p>Web resource: running</p>
40+
<p>API response: {apiMessage}</p>
41+
</body>
42+
</html>
43+
""";
44+
45+
return Results.Content(html, "text/html");
46+
});
47+
48+
app.Run();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<Solution>
2+
<Project Path="AspireOrchestrationMinimal.AppHost/AspireOrchestrationMinimal.AppHost.csproj" />
3+
<Project Path="AspireOrchestrationMinimal.Api/AspireOrchestrationMinimal.Api.csproj" />
4+
<Project Path="AspireOrchestrationMinimal.Web/AspireOrchestrationMinimal.Web.csproj" />
5+
</Solution>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Aspire AppHost Orchestration &mdash; Minimal Sample
2+
3+
A minimal .NET 10 Aspire sample demonstrating how one AppHost starts two .NET
4+
projects, connects them with a logical reference, and enables the web project
5+
to call the API through service discovery without a fixed localhost port.
6+
7+
## Full tutorial
8+
9+
[Aspire in .NET: Orchestrate, Run, and Deploy a Distributed System from One App Host](https://www.dotnet-guide.com/tutorials/dotnet-aspire/orchestrate-distributed-system/)
10+
11+
## What this sample demonstrates
12+
13+
* one Aspire AppHost;
14+
* one API project;
15+
* one web project;
16+
* service discovery through `https+http://api`;
17+
* project references and `WithReference(api)`;
18+
* startup ordering with `WaitFor(api)`;
19+
* an externally reachable web endpoint through `WithExternalHttpEndpoints`;
20+
* dashboard visibility of both resources;
21+
* web-to-API communication resolved by the AppHost.
22+
23+
## Architecture
24+
25+
```text
26+
AppHost
27+
├── api ── GET /message
28+
└── web ── GET / (calls api through service discovery)
29+
│
30+
└── WithReference(api)
31+
└── WaitFor(api)
32+
```
33+
34+
## File structure
35+
36+
```text
37+
orchestrate-distributed-system/
38+
├── AspireOrchestrationMinimal.slnx
39+
├── README.md
40+
├── AspireOrchestrationMinimal.AppHost/
41+
│ ├── AppHost.cs
42+
│ └── AspireOrchestrationMinimal.AppHost.csproj
43+
├── AspireOrchestrationMinimal.Api/
44+
│ ├── Program.cs
45+
│ └── AspireOrchestrationMinimal.Api.csproj
46+
└── AspireOrchestrationMinimal.Web/
47+
├── Program.cs
48+
└── AspireOrchestrationMinimal.Web.csproj
49+
```
50+
51+
## Prerequisites
52+
53+
* .NET 10 SDK
54+
* HTTPS development certificate trusted (run `dotnet dev-certs https --trust`)
55+
56+
No Docker, Podman, PostgreSQL, Redis, API keys, or cloud credentials are required.
57+
58+
## Run the sample
59+
60+
Open a terminal in this sample folder and run:
61+
62+
```powershell
63+
dotnet restore
64+
dotnet build --configuration Release
65+
dotnet run --project AspireOrchestrationMinimal.AppHost
66+
```
67+
68+
The AppHost starts both projects. The Aspire dashboard opens automatically.
69+
70+
## Verify the sample
71+
72+
### Dashboard
73+
74+
Confirm that the Aspire dashboard shows:
75+
76+
* `api` &mdash; started successfully
77+
* `web` &mdash; started successfully, waited for `api`
78+
* both endpoints are visible
79+
80+
### Web endpoint
81+
82+
Open the web endpoint shown in the dashboard. The page displays:
83+
84+
```text
85+
Aspire orchestration sample
86+
87+
Web resource: running
88+
API response: {"message":"Hello from the Aspire-managed API","service":"api"}
89+
```
90+
91+
### API endpoint
92+
93+
```text
94+
GET / → AspireOrchestrationMinimal.Api is running.
95+
GET /message → {"message":"Hello from the Aspire-managed API","service":"api"}
96+
```
97+
98+
## Service discovery
99+
100+
The web project uses:
101+
102+
```csharp
103+
client.BaseAddress = new Uri("https+http://api");
104+
```
105+
106+
The resource name `api` matches the AppHost declaration:
107+
108+
```csharp
109+
var api = builder.AddProject<Projects.AspireOrchestrationMinimal_Api>("api");
110+
```
111+
112+
Aspire resolves the logical name at runtime. The web project contains **no**
113+
fixed localhost port such as `http://localhost:5001`.
114+
115+
## `WithReference` versus `WaitFor`
116+
117+
| Method | Purpose |
118+
| --- | --- |
119+
| `WithReference(api)` | Supplies service-discovery and configuration information to the referencing project |
120+
| `WaitFor(api)` | Controls startup ordering — the web project does not start until the API is ready |
121+
122+
They solve different problems. This sample uses both.
123+
124+
## Important boundary
125+
126+
This repository sample intentionally does **not** reproduce the complete tutorial.
127+
128+
The full DOTNET GUIDE tutorial additionally contains:
129+
130+
* PostgreSQL and Redis integration
131+
* ServiceDefaults project
132+
* EF Core and Npgsql
133+
* database migrations
134+
* caching
135+
* custom health-check and readiness packages
136+
* `Aspire.Hosting.Testing`
137+
* integration-test projects
138+
* OpenTelemetry customization
139+
* generative-AI telemetry
140+
* publishing (`aspire publish`)
141+
* deployment (`aspire deploy`)
142+
* Azure deployment guidance
143+
* Kubernetes and Helm
144+
* Docker-oriented publishing
145+
* production secrets, ingress, scaling, and networking
146+
* complete release-management and production-hardening guidance
147+
148+
## Verification
149+
150+
| Item | Detail |
151+
| --- | --- |
152+
| Target framework | .NET 10 (`net10.0`) |
153+
| SDK | .NET SDK 10.0.302 |
154+
| Aspire version | `Aspire.Hosting.AppHost` 13.4.6 |
155+
| Service discovery | `Microsoft.Extensions.ServiceDiscovery` 10.8.0 |
156+
| External services required | None |
157+
| Containers required | None |
158+
| API keys required | None |
159+
| Local restore | Verified |
160+
| Release build | Verified |
161+
| Last reviewed | 2026-07-26 |
162+
163+
## License
164+
165+
Repository-owned sample code is available under the [MIT License](../../LICENSE).
166+
167+
Third-party Aspire and .NET packages remain subject to their own licenses.

0 commit comments

Comments
 (0)