💻 Développement

dev-dotnet-aspire-guide

Guide .NET Aspire pour l'orchestration d'applications cloud-native, le service discovery, la télémétrie et l'intégration de composants distribués.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-dotnet-aspire-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-dotnet-aspire-guide -Launch"

🚀 Déjà installé ?

claude "/dev-dotnet-aspire-guide"

Ou tapez /dev-dotnet-aspire-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

.NET AspireAspireAppHostservice discovery .NETorchestration cloud-nativeAspire dashboard

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-dotnet-aspire-guide ~/.claude/skills/

Payload du plugin : skills/dev-dotnet-aspire-guide · source éditable : dev-skills/dotnet-aspire-guide

📖 Manuel

Guide .NET Aspire

Quand utiliser .NET Aspire

SituationRecommandation
≥ 2 services qui se parlent en localAspire — service discovery automatique
Besoin de traces distribuées dès le devAspire — dashboard OTEL intégré
Projet mono-service simpleAspire superflu
Cible Azure Container Apps / KubernetesAspire — manifeste généré via azd ou aspirate
Équipe < 3 devs, pas de messagingÀ évaluer ; Aspire a un coût d'apprentissage

Version stable 2026 : .NET Aspire 9.x (compatible .NET 8 et .NET 9).


Étape 1 — Créer le squelette

# Prérequis : Docker Desktop actif, workload Aspire installé
dotnet workload install aspire

# Nouveau projet depuis le template
dotnet new aspire-starter -n MySolution -o MySolution
cd MySolution

# Structure générée
# MySolution.AppHost/          orchestrateur
# MySolution.ServiceDefaults/  config partagée
# MySolution.ApiService/       API demo
# MySolution.Web/              Blazor frontend

Pour ajouter Aspire à une solution existante :

dotnet new aspire-apphost -n MySolution.AppHost
dotnet new aspire-servicedefaults -n MySolution.ServiceDefaults
# Puis ajouter les références projets manuellement

Étape 2 — Câbler l'AppHost

L'AppHost est le point d'entrée unique. Il déclare les ressources (infra) et les projets (code), puis les relie.

// MySolution.AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);

// --- Infrastructure ---
var postgres = builder.AddPostgres("postgres")
    .WithDataVolume("pgdata")   // volume nommé = persistance entre runs
    .WithPgAdmin();             // UI admin sur port aléatoire

var orderDb = postgres.AddDatabase("orderdb");
var redis   = builder.AddRedis("cache").WithRedisCommander();
var bus     = builder.AddRabbitMQ("messaging").WithManagementPlugin();

// --- Services applicatifs ---
var api = builder.AddProject<Projects.MySolution_ApiService>("api")
    .WithReference(orderDb)     // injecte la conn string via env var
    .WithReference(redis)
    .WithReference(bus)
    .WithReplicas(2);           // 2 instances en dev pour tester la LB

builder.AddProject<Projects.MySolution_Web>("web")
    .WithExternalHttpEndpoints()
    .WithReference(api);        // URL résolue par service discovery

builder.Build().Run();

Règle : nommez les ressources en minuscules-tirets ("order-db"), ce nom devient la clé de service discovery.


Étape 3 — ServiceDefaults (configuration partagée)

Chaque service appelle AddServiceDefaults() dans son Program.cs. Ce package centralise : OpenTelemetry, health checks, resilience, service discovery.

// Dans chaque service : Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); // une seule ligne suffit
// MySolution.ServiceDefaults/Extensions.cs
public static IHostApplicationBuilder AddServiceDefaults(
    this IHostApplicationBuilder builder)
{
    builder.ConfigureOpenTelemetry();
    builder.AddDefaultHealthChecks();

    builder.Services.AddServiceDiscovery();
    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        http.AddStandardResilienceHandler(); // retry + circuit breaker
        http.AddServiceDiscovery();
    });
    return builder;
}

public static IHostApplicationBuilder ConfigureOpenTelemetry(
    this IHostApplicationBuilder builder)
{
    builder.Logging.AddOpenTelemetry(o =>
    {
        o.IncludeFormattedMessage = true;
        o.IncludeScopes = true;
    });

    builder.Services.AddOpenTelemetry()
        .WithMetrics(m => m
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation())
        .WithTracing(t => t
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddEntityFrameworkCoreInstrumentation());

    builder.AddOpenTelemetryExporters(); // OTLP vers le dashboard Aspire
    return builder;
}

Étape 4 — Service Discovery dans les services

// Résolution automatique du nom logique déclaré dans AppHost
builder.Services.AddHttpClient<IOrderServiceClient>(client =>
{
    // "https+http://api" = nom donné dans AddProject("api")
    // Aspire résout l'URL réelle (port dynamique) à l'exécution
    client.BaseAddress = new Uri("https+http://api");
});
// Connexion PostgreSQL injectée automatiquement via Aspire
builder.AddNpgsqlDataSource("orderdb"); // "orderdb" = nom de la database dans AppHost

Composants intégrés (Aspire 9)

TechnologiePackage NuGet (AppHost)Package NuGet (Service)Méthode service
PostgreSQLAspire.Hosting.PostgreSQLAspire.NpgsqlAddNpgsqlDataSource
SQL ServerAspire.Hosting.SqlServerAspire.Microsoft.Data.SqlClientAddSqlServerClient
RedisAspire.Hosting.RedisAspire.StackExchange.RedisAddRedisClient
RabbitMQAspire.Hosting.RabbitMQAspire.RabbitMQ.ClientAddRabbitMQClient
MongoDBAspire.Hosting.MongoDBAspire.MongoDB.DriverAddMongoDBClient
KafkaAspire.Hosting.KafkaAspire.Confluent.KafkaAddKafkaProducer
Azure BlobAspire.Hosting.Azure.StorageAspire.Azure.Storage.BlobsAddAzureBlobClient
Azure Service BusAspire.Hosting.Azure.ServiceBusAspire.Azure.Messaging.ServiceBusAddAzureServiceBusClient
ElasticsearchAspire.Hosting.ElasticsearchAspire.Elastic.Clients.ElasticsearchAddElasticsearchClient

Étape 5 — Dashboard Aspire

Lancé automatiquement avec dotnet run sur l'AppHost :

VueUsage
ResourcesÉtat (Running/Stopped/Unhealthy) de chaque conteneur/projet
Console LogsStdout/stderr en temps réel, filtrable par service
Structured LogsLogs JSON avec filtres sur niveau/message/attributs
TracesTraces distribuées OTEL — waterfall inter-services
MetricsMétriques runtime + applicatives (graphs)

URL par défaut : https://localhost:17088 (token dans la console au démarrage).


Étape 6 — Déploiement

Azure Container Apps (recommandé)

dotnet tool install -g azd
azd init          # détecte Aspire, génère azure.yaml
azd up            # provision + deploy en une commande

Kubernetes (via Aspirate)

dotnet tool install -g aspirate
aspirate init
aspirate generate  # génère les manifests K8s
aspirate apply     # kubectl apply automatique

Production : remplacer les conteneurs locaux par des services managés

// AppHost/Program.cs — switch dev/prod
if (builder.ExecutionContext.IsPublishMode)
{
    // Azure SQL Managed Instance en prod
    var sql = builder.AddAzureSqlServer("sql")
                     .AddDatabase("orderdb");
}
else
{
    // SQL Server local en conteneur en dev
    var sql = builder.AddSqlServer("sql")
                     .AddDatabase("orderdb");
}

Anti-patterns / Pièges

ProblèmeCauseCorrection
Service discovery échoueNom dans AddHttpClient ne correspond pas au nom AppHostUtiliser exactement le même nom logique
Port déjà utilisé au démarrageAspire affecte des ports dynamiques mais les profils launchSettings peuvent fixer les portsSupprimer les applicationUrl dans launchSettings.json des services
Données perdues entre redémarragesVolume anonyme (sans .WithDataVolume("nom"))Toujours nommer les volumes
Traces manquantesAddServiceDefaults() non appelé dans un serviceVérifier que chaque service l'appelle
Container pull lent en CIImages téléchargées à chaque runPré-pull les images dans le step CI ou utiliser un registry miroir
WithReplicas en prodLes réplicas Aspire sont pour le dev uniquementEn prod, déléguer le scaling à ACA/K8s
Secrets en clair dans AppHost.WithEnvironment("API_KEY", "secret") visible dans les logs AspireUtiliser builder.AddParameter("api-key", secret: true)

Bonnes pratiques 2026

```csharp // Test d'intégration avec DistributedApplicationTestingBuilder await using var app = await DistributedApplicationTestingBuilder .CreateAsync<Projects.MySolution_AppHost>(); await app.StartAsync(); var httpClient = app.CreateHttpClient("api"); var response = await httpClient.GetAsync("/health"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); ```