.NET & C# Development · Lesson 163 of 229
.NET Aspire — Cloud-Native Orchestration and Service Discovery
.NET Aspire — Cloud-Native Orchestration and Service Discovery
.NET Aspire is a stack for building observable, production-ready distributed .NET applications. It wires up service discovery, OpenTelemetry, health checks, and local orchestration without Docker Compose configuration.
What Aspire Solves
Without Aspire:
- Write docker-compose.yml to run Redis, PostgreSQL, RabbitMQ locally
- Configure OpenTelemetry manually in each service
- Hard-code service URLs or use a local .env file
- No local dashboard for traces, logs, and metrics
With Aspire:
- AppHost project defines the distributed app in C#
- Services discover each other by name — no hard-coded URLs
- Redis, PostgreSQL, RabbitMQ are provisioned automatically (containers or cloud)
- Dashboard: traces, logs, structured events, resource health — in a browserStep 1: Create an Aspire Solution
# Install the template
dotnet new install Aspire.ProjectTemplates
# Create a solution with AppHost, ServiceDefaults, and two services
dotnet new aspire-starter --name MyApp
# Or add Aspire to an existing solution:
dotnet new aspire-apphost -n MyApp.AppHost
dotnet new aspire-servicedefaults -n MyApp.ServiceDefaultsStep 2: AppHost — Define the Distributed App
// MyApp.AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
// Infrastructure components
var postgres = builder.AddPostgres("postgres")
.WithDataVolume() // persist data across restarts
.WithPgAdmin(); // launch pgAdmin dashboard
var database = postgres.AddDatabase("orderdb");
var redis = builder.AddRedis("cache")
.WithRedisInsight(); // launch RedisInsight dashboard
var rabbitMq = builder.AddRabbitMQ("messaging")
.WithManagementPlugin(); // RabbitMQ management UI
// Application services
var orderService = builder.AddProject<Projects.OrderService>("order-service")
.WithReference(database) // injects connection string
.WithReference(redis) // injects Redis connection
.WithReference(rabbitMq); // injects RabbitMQ connection
var apiGateway = builder.AddProject<Projects.ApiGateway>("api-gateway")
.WithReference(orderService) // injects service URL for discovery
.WithExternalHttpEndpoints(); // expose to browser
builder.Build().Run();Step 3: ServiceDefaults — Shared Configuration
// MyApp.ServiceDefaults/Extensions.cs
// This is generated — add to every service
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
// OpenTelemetry — traces, metrics, logs
builder.ConfigureOpenTelemetry();
// Health checks — /health/live and /health/ready
builder.AddDefaultHealthChecks();
// Service discovery — resolve other services by name
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler(); // retry, circuit breaker, timeout
http.AddServiceDiscovery();
});
return builder;
}
}// Every service Program.cs — one line to get everything
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); // OpenTelemetry + health checks + service discovery
// Use Aspire integrations
builder.AddNpgsqlDbContext<AppDbContext>("orderdb"); // connection string from AppHost
builder.AddRedisDistributedCache("cache"); // Redis from AppHost
builder.AddRabbitMQClient("messaging"); // RabbitMQ from AppHostStep 4: Service Discovery
// AppHost injects service URLs as environment variables
// Order service URL: "http://order-service" — resolved by Aspire's DNS
// In ApiGateway — call OrderService by name, no hard-coded URL
builder.Services.AddHttpClient<OrderServiceClient>(client =>
{
// "http://order-service" is resolved by Aspire service discovery
client.BaseAddress = new Uri("http://order-service");
});
// Or use the strongly-typed approach from AppHost reference:
// WithReference(orderService) injects:
// services__order-service__http__0 = "http://localhost:5001"
// The service discovery infrastructure resolves "http://order-service" → "http://localhost:5001"Step 5: The Aspire Dashboard
Run the AppHost and open: https://localhost:15888
Dashboard shows:
Resources: all services and containers with health status
Console logs: structured logs from every service, filterable
Structured: parsed log events with properties
Traces: distributed traces — waterfall view across all services
Metrics: CPU, memory, request rate, error rate per service
Everything goes through OpenTelemetry — no extra setup needed.Aspire Integrations (Built-in)
// Aspire has first-class integrations — one line to add each:
// PostgreSQL with EF Core
builder.AddNpgsqlDbContext<AppDbContext>("orderdb");
// Redis
builder.AddRedisDistributedCache("cache");
builder.AddRedisOutputCache("cache");
// Azure Service Bus
builder.AddAzureServiceBusClient("servicebus");
// Azure Blob Storage
builder.AddAzureBlobServiceClient("blobs");
// SQL Server
builder.AddSqlServerDbContext<AppDbContext>("sqldb");
// MongoDB
builder.AddMongoDBClient("mongodb");
// RabbitMQ
builder.AddRabbitMQClient("messaging");
// Each integration:
// - Reads connection string injected by AppHost
// - Adds health checks automatically
// - Adds OpenTelemetry instrumentation
// - Registers in DIDeploying to Azure Container Apps
# Install Azure Developer CLI
winget install Microsoft.Azd
# Initialize Aspire deployment
azd init
# Provision and deploy
azd up
# Aspire → AZD generates:
# - Bicep templates for Azure Container Apps environment
# - Managed Identity for each service
# - Azure Container Registry for images
# - Azure Key Vault for secrets
# - Azure Monitor / Application Insights for observability// AppHost: swap local containers for Azure resources in production
var postgres = builder.AddAzurePostgresFlexibleServer("postgres")
.AddDatabase("orderdb");
var redis = builder.AddAzureRedis("cache");
var serviceBus = builder.AddAzureServiceBus("messaging");
// Same service code — only AppHost changes between environmentsResilience — Automatic Retry and Circuit Breaker
// AddStandardResilienceHandler (from ServiceDefaults) adds:
// - Total request timeout: 30s
// - Per-attempt timeout: 10s
// - Retry: up to 3 times with exponential backoff + jitter
// - Circuit breaker: opens after 10 failures in 30s
// Customise for a specific client:
builder.Services.AddHttpClient<InventoryClient>(client =>
client.BaseAddress = new Uri("http://inventory-service"))
.AddResilienceHandler("inventory", pipeline =>
{
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 5,
BackoffType = DelayBackoffType.Exponential,
});
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
SamplingDuration = TimeSpan.FromSeconds(10),
FailureRatio = 0.5,
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
});
});Interview Answer
".NET Aspire is Microsoft's cloud-native application stack for .NET distributed applications. The AppHost project defines the entire system in C# — services, databases, caches, message brokers — and Aspire runs them locally as containers or processes. Services discover each other by name (AddServiceDiscovery) with no hard-coded URLs. ServiceDefaults is a shared project added to every service that wires up OpenTelemetry, health checks, service discovery, and the standard HTTP resilience pipeline (retry + circuit breaker + timeout) in one call. The local Aspire dashboard shows traces, logs, metrics, and resource health across all services in a browser — no Jaeger or Grafana setup needed locally. Aspire integrations (AddNpgsqlDbContext, AddRedisDistributedCache) read connection strings injected by the AppHost and automatically add health checks and telemetry. For production deployment, azd up generates Azure Container Apps infrastructure with Managed Identity, Azure Monitor, and Key Vault."