원클릭으로
dotnet-aspire-patterns
Using .NET Aspire. AppHost orchestration, service discovery, components, dashboard, health checks.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Using .NET Aspire. AppHost orchestration, service discovery, components, dashboard, health checks.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Guidelines for writing TUnit tests in .NET, including setup, assertions, async testing, and best practices. Use when writing unit tests with TUnit framework, setting up TUnit in a .NET project, or migrating from other test frameworks to TUnit.
Hosted services, background jobs, outbox patterns, and graceful shutdown handling for ASP.NET Core applications. Includes patterns for reliable job processing, distributed systems, and lifecycle management. Use when implementing background processing in ASP.NET Core applications, handling outbox patterns for reliable message delivery, or managing graceful service shutdown.
Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, versioning, naming conventions, parameter ordering, and return types for NuGet packages and distributed systems. Use when designing public APIs for NuGet packages or libraries, making changes to existing public APIs, planning wire format changes for distributed systems, or reviewing pull requests for breaking changes.
Write modern, high-performance C# code using records, pattern matching, value objects, async/await, Span<T>/Memory<T>, and best-practice API design patterns. Emphasizes functional-style programming with C# 12+ features. Use when writing new C# code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.
Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Covers both high-level abstractions and low-level synchronization primitives. Use when deciding how to handle concurrent operations in .NET, evaluating whether to use async/await, Channels, or Akka.NET, or managing state across multiple concurrent entities.
Design .NET types for performance. Covers struct vs class decision matrix, sealed by default, readonly structs, ref struct and Span/Memory selection, FrozenDictionary, ValueTask, and collection return types. Use when designing new types and APIs, reviewing code for performance issues, choosing between class, struct, and record, or working with collections and enumerables.
SOC 직업 분류 기준
| name | dotnet-aspire-patterns |
| description | Using .NET Aspire. AppHost orchestration, service discovery, components, dashboard, health checks. |
.NET Aspire orchestration patterns for building cloud-ready distributed applications. Covers AppHost configuration, service discovery, the component model for integrating backing services (databases, caches, message brokers), the Aspire dashboard for local observability, distributed health checks, and when to choose Aspire vs manual container orchestration.
Out of scope: Raw Dockerfile authoring and multi-stage builds -- see [skill:dotnet-containers]. Kubernetes manifests, Helm charts, and Docker Compose -- see [skill:dotnet-container-deployment]. OpenTelemetry SDK configuration and custom metrics -- see [skill:dotnet-observability]. DI service lifetime mechanics -- see [skill:dotnet-csharp-dependency-injection]. Background service hosting -- see [skill:dotnet-background-services].
Cross-references: [skill:dotnet-containers] for container image optimization and base image selection, [skill:dotnet-container-deployment] for production Kubernetes/Compose deployment, [skill:dotnet-observability] for OpenTelemetry details beyond Aspire defaults, [skill:dotnet-csharp-dependency-injection] for DI fundamentals, [skill:dotnet-background-services] for hosted service lifecycle patterns.
.NET Aspire is an opinionated stack for building observable, production-ready distributed applications. It provides:
Aspire is not a deployment target. It orchestrates the local development and testing experience. For production, it generates manifests consumed by deployment tools (Azure Developer CLI, Kubernetes, etc.).
| Scenario | Recommendation |
|---|---|
| Multiple .NET services + backing infrastructure | Aspire AppHost -- simplifies local dev and service wiring |
| Single API with a database | Optional -- Aspire adds overhead for simple topologies |
| Non-.NET services only (Node, Python) | Aspire can reference container images, but the tooling benefit is reduced |
| Need Kubernetes/Compose for local dev already | Evaluate migration cost; Aspire replaces docker-compose for dev scenarios |
| Team needs consistent observability defaults | Aspire ServiceDefaults standardize OTel across all projects |
The AppHost is a .NET project (Aspire.Hosting.AppHost SDK) that defines the distributed application topology. It references other projects and backing services, wiring them together with service discovery.
<Project Sdk="Microsoft.NET.Sdk">
<!-- Aspire SDK version is independent of .NET TFM; 9.x works on net8.0+ -->
<Sdk Name="Aspire.AppHost.Sdk" Version="9.1.*" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.1.*" />
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.1.*" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.1.*" />
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.1.*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApi\MyApi.csproj" />
<ProjectReference Include="..\MyWorker\MyWorker.csproj" />
</ItemGroup>
</Project>
var builder = DistributedApplication.CreateBuilder(args);
// Backing services -- Aspire manages containers automatically
var postgres = builder.AddPostgres("pg")
.WithPgAdmin() // Adds pgAdmin UI container
.AddDatabase("ordersdb");
var redis = builder.AddRedis("cache")
.WithRedisCommander(); // Adds Redis Commander UI
var rabbitmq = builder.AddRabbitMQ("messaging")
.WithManagementPlugin(); // Adds RabbitMQ management UI
// Application projects -- wired with service discovery
var api = builder.AddProject<Projects.MyApi>("api")
.WithReference(postgres)
.WithReference(redis)
.WithReference(rabbitmq)
.WithExternalHttpEndpoints(); // Marks endpoints as public in deployment manifests
builder.AddProject<Projects.MyWorker>("worker")
.WithReference(postgres)
.WithReference(rabbitmq)
.WaitFor(api); // Start worker after API is healthy
builder.Build().Run();
WaitFor controls startup ordering. Resources wait until dependencies report healthy before starting:
// Worker waits for both the database and API to be ready
builder.AddProject<Projects.MyWorker>("worker")
.WithReference(postgres)
.WaitFor(postgres) // Wait for database container health check
.WaitFor(api); // Wait for API health endpoint
Without WaitFor, resources start in parallel. Use it only when startup order matters (e.g., a worker that requires the database schema to exist).
Aspire automatically configures service discovery so projects can resolve each other by resource name rather than hardcoded URLs.
Aspire.ServiceDefaults project configures Microsoft.Extensions.ServiceDiscoveryHttpClient or connection strings// In MyApi/Program.cs
var builder = WebApplication.CreateBuilder(args);
// AddServiceDefaults registers service discovery, OpenTelemetry, health checks
builder.AddServiceDefaults();
// HttpClient resolves "worker" via service discovery
builder.Services.AddHttpClient("worker-client", client =>
{
client.BaseAddress = new Uri("https+http://worker");
});
The https+http:// scheme prefix tells the service discovery provider to try HTTPS first, falling back to HTTP. This is the recommended pattern for inter-service communication in Aspire.
For backing services (databases, caches), Aspire injects connection strings via the standard ConnectionStrings configuration section:
// AppHost: .WithReference(postgres) on the API project
// injects ConnectionStrings__ordersdb automatically
// In MyApi/Program.cs
builder.AddNpgsqlDbContext<OrdersDbContext>("ordersdb");
// Resolves ConnectionStrings:ordersdb from configuration
Aspire components are NuGet packages that provide pre-configured client integrations for backing services. They handle connection management, health checks, telemetry, and resilience.
| Package Type | Installed In | Purpose |
|---|---|---|
Aspire.Hosting.* | AppHost project | Define and configure the resource (container, connection) |
Aspire.* (client) | Service projects | Consume the resource with health checks and telemetry |
<!-- AppHost project -->
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="9.1.*" />
<!-- API project -->
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.1.*" />
| Component | Hosting Package | Client Package |
|---|---|---|
| PostgreSQL (EF Core) | Aspire.Hosting.PostgreSQL | Aspire.Npgsql.EntityFrameworkCore.PostgreSQL |
| PostgreSQL (Npgsql) | Aspire.Hosting.PostgreSQL | Aspire.Npgsql |
| Redis (caching) | Aspire.Hosting.Redis | Aspire.StackExchange.Redis |
| Redis (output cache) | Aspire.Hosting.Redis | Aspire.StackExchange.Redis.OutputCaching |
| RabbitMQ | Aspire.Hosting.RabbitMQ | Aspire.RabbitMQ.Client |
| Azure Service Bus | Aspire.Hosting.Azure.ServiceBus | Aspire.Azure.Messaging.ServiceBus |
| SQL Server (EF Core) | Aspire.Hosting.SqlServer | Aspire.Microsoft.EntityFrameworkCore.SqlServer |
| MongoDB | Aspire.Hosting.MongoDB | Aspire.MongoDB.Driver |
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Each Add* method registers the client, health check, and telemetry
builder.AddNpgsqlDbContext<OrdersDbContext>("ordersdb");
builder.AddRedisClient("cache");
builder.AddRabbitMQClient("messaging");
Component Add* methods:
The ServiceDefaults project is a shared library referenced by all service projects. It standardizes cross-cutting concerns.
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(
this IHostApplicationBuilder builder)
{
// Service discovery
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
// Resilience for HttpClient
builder.Services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler();
http.AddServiceDiscovery();
});
return builder;
}
public static IHostApplicationBuilder ConfigureOpenTelemetry(
this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation())
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation());
builder.AddOpenTelemetryExporters();
return builder;
}
public static IHostApplicationBuilder AddDefaultHealthChecks(
this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy());
return builder;
}
public static WebApplication MapDefaultEndpoints(
this WebApplication app)
{
app.MapHealthChecks("/health");
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
return app;
}
}
Every service project references the ServiceDefaults project and calls the extension methods:
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// ... service-specific registrations
var app = builder.Build();
app.MapDefaultEndpoints();
// ... middleware and endpoints
app.Run();
The Aspire dashboard provides a local observability UI that starts automatically with the AppHost. It displays:
When you run the AppHost (dotnet run --project MyApp.AppHost), the dashboard URL is printed to the console:
info: Aspire.Hosting.DistributedApplication[0]
Login to the dashboard at https://localhost:17043/login?t=<token>
The dashboard is available as a standalone container for projects not using the full Aspire stack:
docker run --rm -it -p 18888:18888 -p 4317:18889 \
-d --name aspire-dashboard \
mcr.microsoft.com/dotnet/aspire-dashboard:9.1
Configure your app to export OTLP telemetry to http://localhost:4317 and view it at http://localhost:18888.
Each Aspire component automatically registers health checks. The AppHost uses these to determine resource readiness:
// In AppHost -- WaitFor uses health checks to gate startup
builder.AddProject<Projects.MyApi>("api")
.WithReference(postgres)
.WaitFor(postgres); // Waits for Npgsql health check to pass
Add application-specific health checks alongside Aspire defaults:
builder.Services.AddHealthChecks()
.AddCheck<OrderProcessingHealthCheck>(
"order-processing",
tags: ["ready"]);
See [skill:dotnet-observability] for detailed health check patterns (liveness vs readiness, custom checks, health check publishing).
Aspire configures OpenTelemetry tracing through ServiceDefaults. Traces propagate automatically across HTTP boundaries. For custom spans:
private static readonly ActivitySource s_activitySource = new("MyApp.Orders");
public async Task<Order> ProcessOrderAsync(CreateOrderRequest request, CancellationToken ct)
{
using var activity = s_activitySource.StartActivity("ProcessOrder");
activity?.SetTag("order.customer_id", request.CustomerId);
// Calls to other Aspire services carry trace context automatically
var inventory = await _httpClient.GetFromJsonAsync<InventoryResponse>(
$"https+http://inventory-api/api/stock/{request.ProductId}", ct);
// ... process order
return order;
}
See [skill:dotnet-observability] for comprehensive distributed tracing guidance (custom ActivitySource, trace context propagation, span events).
For services not available as Aspire components, add arbitrary container images:
var seq = builder.AddContainer("seq", "datalust/seq")
.WithHttpEndpoint(port: 5341, targetPort: 80)
.WithEnvironment("ACCEPT_EULA", "Y");
// Reference the container from a project
builder.AddProject<Projects.MyApi>("api")
.WithReference(seq);
By default, Aspire containers use ephemeral storage. Add volumes for data persistence across restarts:
var postgres = builder.AddPostgres("pg")
.WithDataVolume("pg-data") // Named volume for data persistence
.AddDatabase("ordersdb");
Reference existing infrastructure not managed by Aspire:
// Connection string from configuration (not an Aspire-managed container)
var existingDb = builder.AddConnectionString("legacydb");
builder.AddProject<Projects.MyApi>("api")
.WithReference(existingDb);
| Concern | Aspire | Docker Compose / Manual |
|---|---|---|
| Configuration language | C# (strongly typed) | YAML |
| Service discovery | Automatic (env var injection) | Manual DNS/env config |
| Health checks | Automatic per component | Manual HEALTHCHECK per service |
| Observability | Pre-configured OTel + dashboard | Manual OTel collector setup |
| IDE integration | Hot reload, F5 debugging | Attach debugger manually |
| Production deployment | Generates manifests (AZD, K8s) | Write manifests directly |
| Non-.NET services | Container references (less integrated) | Equal support for all languages |
| Learning curve | .NET-specific abstractions | Industry-standard tooling |
Choose Aspire when your stack is primarily .NET and you want standardized observability, service discovery, and a simplified local dev experience. Choose manual orchestration when you need fine-grained control, polyglot services, or your team is already proficient with Compose/Kubernetes.
.AddOpenTelemetry() calls causes duplicate trace/metric collection and inflated telemetry costs.builder.AddNpgsqlDbContext<T>("name") or builder.Configuration.GetConnectionString("name"). Aspire injects connection strings via environment variables; hardcoded values bypass service discovery.WaitFor on every resource -- it serializes startup and increases launch time. Use it only when a service genuinely cannot start without the dependency (e.g., database migration on startup).Aspire.Hosting.* packages from service projects -- hosting packages belong in the AppHost only. Service projects use client packages (Aspire.Npgsql, Aspire.StackExchange.Redis, etc.).AddServiceDefaults() in new service projects -- without it, the project lacks service discovery, health checks, and telemetry, breaking Aspire integration silently.dotnet workload install aspire