From 9d794e04590f38c90db8eac2fb75ddaa9ad17293 Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Sun, 26 Jul 2026 11:54:01 +0000 Subject: [PATCH 1/3] Add lightweight Aspire orchestration sample --- .../AspireOrchestrationMinimal.Api.csproj | 9 + .../AspireOrchestrationMinimal.Api/Program.cs | 13 ++ .../AppHost.cs | 12 ++ .../AspireOrchestrationMinimal.AppHost.csproj | 16 ++ .../AspireOrchestrationMinimal.Web.csproj | 13 ++ .../AspireOrchestrationMinimal.Web/Program.cs | 48 +++++ .../AspireOrchestrationMinimal.slnx | 5 + .../orchestrate-distributed-system/README.md | 167 ++++++++++++++++++ 8 files changed, 283 insertions(+) create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/AspireOrchestrationMinimal.Api.csproj create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/Program.cs create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AppHost.cs create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AspireOrchestrationMinimal.AppHost.csproj create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/AspireOrchestrationMinimal.Web.csproj create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/Program.cs create mode 100644 dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx create mode 100644 dotnet-aspire/orchestrate-distributed-system/README.md diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/AspireOrchestrationMinimal.Api.csproj b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/AspireOrchestrationMinimal.Api.csproj new file mode 100644 index 0000000..0a79721 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/AspireOrchestrationMinimal.Api.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/Program.cs b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/Program.cs new file mode 100644 index 0000000..342b202 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Api/Program.cs @@ -0,0 +1,13 @@ +var builder = WebApplication.CreateBuilder(args); + +var app = builder.Build(); + +app.MapGet("/", () => "AspireOrchestrationMinimal.Api is running."); + +app.MapGet("/message", () => Results.Ok(new +{ + message = "Hello from the Aspire-managed API", + service = "api" +})); + +app.Run(); \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AppHost.cs b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AppHost.cs new file mode 100644 index 0000000..af0a07b --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AppHost.cs @@ -0,0 +1,12 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var api = builder + .AddProject("api"); + +builder + .AddProject("web") + .WithReference(api) + .WaitFor(api) + .WithExternalHttpEndpoints(); + +builder.Build().Run(); \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AspireOrchestrationMinimal.AppHost.csproj b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AspireOrchestrationMinimal.AppHost.csproj new file mode 100644 index 0000000..296f956 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.AppHost/AspireOrchestrationMinimal.AppHost.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/AspireOrchestrationMinimal.Web.csproj b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/AspireOrchestrationMinimal.Web.csproj new file mode 100644 index 0000000..04f3f19 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/AspireOrchestrationMinimal.Web.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/Program.cs b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/Program.cs new file mode 100644 index 0000000..6cb39c4 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.Web/Program.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.ServiceDiscovery; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddServiceDiscovery(); + +builder.Services.AddHttpClient("api", client => +{ + client.BaseAddress = new Uri("https+http://api"); +}) +.AddServiceDiscovery(); + +var app = builder.Build(); + +app.MapGet("/", async (IHttpClientFactory httpClientFactory) => +{ + var client = httpClientFactory.CreateClient("api"); + + string apiMessage; + + try + { + apiMessage = await client.GetStringAsync("/message"); + } + catch (Exception ex) + { + apiMessage = $"API unavailable: {ex.Message}"; + } + + var html = $""" + + + + + Aspire orchestration sample + + +

Aspire orchestration sample

+

Web resource: running

+

API response: {apiMessage}

+ + + """; + + return Results.Content(html, "text/html"); +}); + +app.Run(); \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx new file mode 100644 index 0000000..6d662a4 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/dotnet-aspire/orchestrate-distributed-system/README.md b/dotnet-aspire/orchestrate-distributed-system/README.md new file mode 100644 index 0000000..c81bbc4 --- /dev/null +++ b/dotnet-aspire/orchestrate-distributed-system/README.md @@ -0,0 +1,167 @@ +# Aspire AppHost Orchestration — Minimal Sample + +A minimal .NET 10 Aspire sample demonstrating how one AppHost starts two .NET +projects, connects them with a logical reference, and enables the web project +to call the API through service discovery without a fixed localhost port. + +## Full tutorial + +[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/) + +## What this sample demonstrates + +* one Aspire AppHost; +* one API project; +* one web project; +* service discovery through `https+http://api`; +* project references and `WithReference(api)`; +* startup ordering with `WaitFor(api)`; +* an externally reachable web endpoint through `WithExternalHttpEndpoints`; +* dashboard visibility of both resources; +* web-to-API communication resolved by the AppHost. + +## Architecture + +```text +AppHost +├── api ── GET /message +└── web ── GET / (calls api through service discovery) + │ + └── WithReference(api) + └── WaitFor(api) +``` + +## File structure + +```text +orchestrate-distributed-system/ +├── AspireOrchestrationMinimal.slnx +├── README.md +├── AspireOrchestrationMinimal.AppHost/ +│ ├── AppHost.cs +│ └── AspireOrchestrationMinimal.AppHost.csproj +├── AspireOrchestrationMinimal.Api/ +│ ├── Program.cs +│ └── AspireOrchestrationMinimal.Api.csproj +└── AspireOrchestrationMinimal.Web/ + ├── Program.cs + └── AspireOrchestrationMinimal.Web.csproj +``` + +## Prerequisites + +* .NET 10 SDK +* HTTPS development certificate trusted (run `dotnet dev-certs https --trust`) + +No Docker, Podman, PostgreSQL, Redis, API keys, or cloud credentials are required. + +## Run the sample + +Open a terminal in this sample folder and run: + +```powershell +dotnet restore +dotnet build --configuration Release +dotnet run --project AspireOrchestrationMinimal.AppHost +``` + +The AppHost starts both projects. The Aspire dashboard opens automatically. + +## Verify the sample + +### Dashboard + +Confirm that the Aspire dashboard shows: + +* `api` — started successfully +* `web` — started successfully, waited for `api` +* both endpoints are visible + +### Web endpoint + +Open the web endpoint shown in the dashboard. The page displays: + +```text +Aspire orchestration sample + +Web resource: running +API response: {"message":"Hello from the Aspire-managed API","service":"api"} +``` + +### API endpoint + +```text +GET / → AspireOrchestrationMinimal.Api is running. +GET /message → {"message":"Hello from the Aspire-managed API","service":"api"} +``` + +## Service discovery + +The web project uses: + +```csharp +client.BaseAddress = new Uri("https+http://api"); +``` + +The resource name `api` matches the AppHost declaration: + +```csharp +var api = builder.AddProject("api"); +``` + +Aspire resolves the logical name at runtime. The web project contains **no** +fixed localhost port such as `http://localhost:5001`. + +## `WithReference` versus `WaitFor` + +| Method | Purpose | +| --- | --- | +| `WithReference(api)` | Supplies service-discovery and configuration information to the referencing project | +| `WaitFor(api)` | Controls startup ordering — the web project does not start until the API is ready | + +They solve different problems. This sample uses both. + +## Important boundary + +This repository sample intentionally does **not** reproduce the complete tutorial. + +The full DOTNET GUIDE tutorial additionally contains: + +* PostgreSQL and Redis integration +* ServiceDefaults project +* EF Core and Npgsql +* database migrations +* caching +* custom health-check and readiness packages +* `Aspire.Hosting.Testing` +* integration-test projects +* OpenTelemetry customization +* generative-AI telemetry +* publishing (`aspire publish`) +* deployment (`aspire deploy`) +* Azure deployment guidance +* Kubernetes and Helm +* Docker-oriented publishing +* production secrets, ingress, scaling, and networking +* complete release-management and production-hardening guidance + +## Verification + +| Item | Detail | +| --- | --- | +| Target framework | .NET 10 (`net10.0`) | +| SDK | .NET SDK 10.0.302 | +| Aspire version | `Aspire.Hosting.AppHost` 13.4.6 | +| Service discovery | `Microsoft.Extensions.ServiceDiscovery` 10.8.0 | +| External services required | None | +| Containers required | None | +| API keys required | None | +| Local restore | Verified | +| Release build | Verified | +| Last reviewed | 2026-07-26 | + +## License + +Repository-owned sample code is available under the [MIT License](../../LICENSE). + +Third-party Aspire and .NET packages remain subject to their own licenses. \ No newline at end of file From 60c095d9a37173d7106abe0a15cebbbd0d4732f0 Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Sun, 26 Jul 2026 11:54:43 +0000 Subject: [PATCH 2/3] Add Aspire sample to repository README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index a2c311d..fcbffb0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Each sample folder contains a focused implementation of one tutorial topic. The | --- | --- | --- | | [`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/) | | [`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/) | +| [`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/) | ## Companion articles @@ -54,6 +55,19 @@ tutorials/ | |-- HybridSearchMinimal.csproj | |-- Program.cs | `-- README.md +|-- dotnet-aspire/ +| `-- orchestrate-distributed-system/ +| |-- AspireOrchestrationMinimal.slnx +| |-- README.md +| |-- AspireOrchestrationMinimal.AppHost/ +| | |-- AppHost.cs +| | `-- AspireOrchestrationMinimal.AppHost.csproj +| |-- AspireOrchestrationMinimal.Api/ +| | |-- Program.cs +| | `-- AspireOrchestrationMinimal.Api.csproj +| `-- AspireOrchestrationMinimal.Web/ +| |-- Program.cs +| `-- AspireOrchestrationMinimal.Web.csproj |-- .github/ | `-- workflows/ | `-- build-samples.yml From 0199ab5eb500b57491e6afebdf67b6a970515257 Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Sun, 26 Jul 2026 11:55:26 +0000 Subject: [PATCH 3/3] Build Aspire sample in GitHub Actions --- .github/workflows/build-samples.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/build-samples.yml b/.github/workflows/build-samples.yml index 4d5ad20..b500112 100644 --- a/.github/workflows/build-samples.yml +++ b/.github/workflows/build-samples.yml @@ -6,12 +6,14 @@ on: paths: - "dotnet-ai/provider-agnostic-chat-gateway/**" - "dotnet-ai/hybrid-search-ef-core-pgvector/**" + - "dotnet-aspire/orchestrate-distributed-system/**" - ".github/workflows/build-samples.yml" pull_request: paths: - "dotnet-ai/provider-agnostic-chat-gateway/**" - "dotnet-ai/hybrid-search-ef-core-pgvector/**" + - "dotnet-aspire/orchestrate-distributed-system/**" - ".github/workflows/build-samples.yml" workflow_dispatch: @@ -76,3 +78,28 @@ jobs: --project dotnet-ai/hybrid-search-ef-core-pgvector/HybridSearchMinimal.csproj \ --configuration Release \ --no-build + + build-aspire-orchestration: + name: Build Aspire orchestration sample + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Install .NET 10 SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: "10.0.x" + + - name: Restore + run: | + dotnet restore \ + dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx + + - name: Build + run: | + dotnet build \ + dotnet-aspire/orchestrate-distributed-system/AspireOrchestrationMinimal.slnx \ + --configuration Release \ + --no-restore