What Aspire Is

Explain what the Aspire AppHost, ServiceDefaults and integrations do, and why the deployed API never depends on Aspire at run time.

Syntax// AppHost var db = builder.AddPostgres("postgres").AddDatabase("ordersdb"); builder.AddProject("api", "../Api/Api.csproj").WithReference(db).WaitFor(db); // API config.GetConnectionString("ordersdb");

Aspire is a set of libraries and tools for running a distributed .NET application on a developer machine: an orders API, its PostgreSQL database, a Redis cache and a worker, started with one command, wired to each other and observable in one dashboard. Version 13 dropped the ".NET" from the name (it was ".NET Aspire" before); the packages in this course are 13.6.3.

The four pieces

PieceWhat it isRuns in production?
AppHostA small C# console project whose Program.cs declares resources: containers, projects, parameters and how they reference each otherNo. It is a development and CI orchestrator
ServiceDefaultsA shared class library with AddServiceDefaults(): OpenTelemetry, health checks, service discovery and HTTP resilienceYes, as ordinary code compiled into each service
IntegrationsHosting packages (Aspire.Hosting.PostgreSQL) used by the AppHost, and client packages (Aspire.Npgsql.EntityFrameworkCore.PostgreSQL) used by servicesClient packages yes, as libraries; hosting packages no
DashboardAn OTLP endpoint and UI showing logs, traces, metrics and resource stateNo, it keeps telemetry in memory

What happens on run

Running the AppHost (dotnet run on it, or aspire run) starts the containers through Docker or Podman, launches each project as a normal process, and starts the dashboard. Each WithReference becomes environment variables on the consuming process: ConnectionStrings__ordersdb for a database, services__inventory__http__0 for another service's endpoint, plus OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME pointing telemetry at the dashboard. Containers get a random host port unless you pin one, and generated passwords are kept in the AppHost's user secrets so they stay stable across runs.

Not a runtime dependency

The API reads builder.Configuration.GetConnectionString("ordersdb"). It does not know whether the value came from an AppHost, a Kubernetes Secret or appsettings.Development.json. That is the design: Aspire produces configuration, and the configuration system you already know consumes it. In production nothing from Aspire runs alongside the API; the platform must supply the same keys.

When a team adopts it

Aspire pays off once there are two or more services plus infrastructure: it replaces a docker-compose file, a page of README set-up steps and per-developer connection strings with code that is compiled, reviewed and identical for everyone. For one API with one database, a compose file or Testcontainers may be all you need. Aspire does not replace your deployment platform; it can generate artifacts for one, covered later in this stage.

Where it goes wrong

  • The API works under the AppHost and crashes in staging, because nobody set ConnectionStrings__ordersdb there. List every key the AppHost injects and check each one exists in each environment.
  • The ServiceDefaults template maps /health and /alive only in Development, so Kubernetes probes get 404 in production until you change it.
  • Telemetry silently disappears in production because OTEL_EXPORTER_OTLP_ENDPOINT is not set: the defaults only add the OTLP exporter when that key exists.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Replace a 40-line docker-compose file and a README of manual steps with an AppHost that starts the orders API, Postgres, Redis and a worker with one command.
  • Onboard a new developer who has Docker installed: clone, run the AppHost, and every connection string and service URL is injected without copying secrets around.
  • Audit which configuration keys staging must supply by reading the AppHost's WithReference calls, before the first deploy fails on a missing ConnectionStrings__ordersdb.

More examples

A minimal AppHost for an orders system

This file is the whole local environment. <code>dotnet run</code> on the AppHost starts a Postgres and a Redis container, starts both APIs as processes and injects <code>ConnectionStrings__ordersdb</code>, <code>ConnectionStrings__cache</code> and <code>services__inventory__http__0</code> into the orders API. A new developer clones the repository, has Docker running, and is productive without copying a single connection string.

Example · csharp
var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres")
    .WithDataVolume();
var ordersDb = postgres.AddDatabase("ordersdb");

var cache = builder.AddRedis("cache");

var inventory = builder.AddProject("inventory", "../Inventory.Api/Inventory.Api.csproj");

builder.AddProject("api", "../Orders.Api/Orders.Api.csproj")
    .WithReference(ordersDb).WaitFor(ordersDb)
    .WithReference(cache).WaitFor(cache)
    .WithReference(inventory)
    .WithExternalHttpEndpoints();

builder.Build().Run();

The solution layout

The AppHost references the service projects; the services never reference the AppHost. That one-way dependency is what keeps Aspire out of production: the container image for <code>Orders.Api</code> contains the API and ServiceDefaults, and nothing that knows how to start Postgres. The <code>deploy/</code> folder is where the real platform configuration lives.

Example · text
Orders.slnx
src/
  Orders.AppHost/            Aspire.AppHost.Sdk; Program.cs declares resources (dev and CI only)
  Orders.ServiceDefaults/    AddServiceDefaults() and MapDefaultEndpoints(), referenced by every service
  Orders.Api/                ASP.NET Core API: references ServiceDefaults, reads ConnectionStrings:ordersdb
  Inventory.Api/             second service, called through service discovery as http://inventory
  Orders.Migrator/           one-shot console app that applies EF Core migrations
tests/
  Orders.Api.Tests/
deploy/
  k8s/                       the production manifests, owned by the team, not by the AppHost

Same keys, supplied by Kubernetes

Production provides exactly the keys the AppHost injected locally, from the platform's own sources: a Secret for the connection strings, a cluster DNS name for the inventory service, and the company's OpenTelemetry collector instead of the dashboard. The image is the same one that ran under the AppHost; only its configuration changes.

Example · yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: orders-api
  template:
    metadata:
      labels:
        app: orders-api
    spec:
      containers:
        - name: api
          image: registry.example.com/orders/api:1.14.2
          env:
            - name: ConnectionStrings__ordersdb
              valueFrom:
                secretKeyRef:
                  name: orders-db
                  key: connection-string
            - name: ConnectionStrings__cache
              valueFrom:
                secretKeyRef:
                  name: orders-redis
                  key: connection-string
            - name: services__inventory__http__0
              value: http://inventory.orders.svc.cluster.local
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: http://otel-collector.observability:4317
            - name: OTEL_SERVICE_NAME
              value: orders-api

Discussion

  • Be the first to comment on this lesson.