Learnixo
Back to blog
Backend Systemsadvanced

.NET Aspire — Cloud-Native Orchestration and Service Discovery

Build cloud-native .NET applications with .NET Aspire: AppHost orchestration, service discovery, observability dashboard, integrations for Redis and PostgreSQL, and deploying to Azure Container Apps.

Asma Hafeez KhanMay 24, 20265 min read
.NETC#Aspirecloud-nativemicroservicesservice discoveryobservability
Share:š•

.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 browser

Step 1: Create an Aspire Solution

Bash
# 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.ServiceDefaults

Step 2: AppHost — Define the Distributed App

C#
// 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

C#
// 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;
    }
}
C#
// 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 AppHost

Step 4: Service Discovery

C#
// 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)

C#
// 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 DI

Deploying to Azure Container Apps

Bash
# 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
C#
// 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 environments

Resilience — Automatic Retry and Circuit Breaker

C#
// 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."

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.