aspire-service-defaults — Claude Code skill for shared Aspire
A skill that scaffolds one shared ServiceDefaults project for an Aspire app, keeping OpenTelemetry, health checks, discovery and HTTP resilience in a single place.
npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a claude-codeWhat aspire-service-defaults does
The skill lays out a MyApp.ServiceDefaults project built around Extensions.cs. One call to AddServiceDefaults() wires OpenTelemetry for logs, traces and metrics, registers a basic health check, turns on service discovery and applies the standard HTTP resilience handler. Every service in the solution calls that method from its Program.cs, so observability config lives in one file instead of being copied around.
ConfigureOpenTelemetry drops requests to /health and /alive from traces, and the OTLP exporter is added only when OTEL_EXPORTER_OTLP_ENDPOINT is set. Set that variable before you expect any telemetry to leave the process. MapDefaultEndpoints publishes the two endpoints and does so only in the development environment.
The example project targets net9.0 with IsAspireSharedProject, and pulls in the resilience, service discovery and OpenTelemetry packages. Worker hosts work too: Host.CreateApplicationBuilder accepts the same extension method.
What it helps with
- set up OpenTelemetry once for every Aspire service
- expose
/healthand/aliveendpoints in each service - apply retry and circuit breaker policies to outgoing HTTP calls
- resolve service names through service discovery
- keep health probe traffic out of traces
Example prompts for the agent
- Create a
MyApp.ServiceDefaultsproject and callAddServiceDefaults()in every service'sProgram.cs - Add the OTLP exporter to ServiceDefaults using the address from
OTEL_EXPORTER_OTLP_ENDPOINT - Map
/healthand/alivewithMapDefaultEndpoints, development environment only
How to install aspire-service-defaults
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a claude-code2Manually: put the files in place
~/.claude/skills/aspire-service-defaults/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/aspire-service-defaults/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a codex2Manually: put the files in place
.agents/skills/aspire-service-defaults/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a cursor2Manually: put the files in place
.cursor/skills/aspire-service-defaults/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Aaronontheweb/dotnet-skills aspire-service-defaults2Manually: put the files in place
.github/skills/aspire-service-defaults/SKILL.md1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a gemini-cli2Platform CLI
gemini skills install https://github.com/Aaronontheweb/dotnet-skills --path skills/aspire-service-defaultsManually: put the files in place
.devin/skills/aspire-service-defaults/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about aspire-service-defaults
How do I install aspire-service-defaults in Claude Code?
Run npx skills add Aaronontheweb/dotnet-skills --skill aspire-service-defaults -a claude-code. The skill lands at ~/.claude/skills/aspire-service-defaults/SKILL.md.
What does aspire-service-defaults do?
It builds a shared ServiceDefaults project that configures OpenTelemetry, health checks, service discovery and HTTP resilience; services pick it up by calling AddServiceDefaults().
Does it need an OTLP collector address?
The exporter is registered only when OTEL_EXPORTER_OTLP_ENDPOINT is set. Without it the app still runs, but traces and metrics are not shipped.
Source text
SKILL.md · MIT
---
name: aspire-service-defaults
description: Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.
user-invocable: false
---
# Aspire Service Defaults
## When to Use This Skill
Use this skill when:
- Building Aspire-based distributed applications
- Need consistent observability (logging, tracing, metrics) across services
- Want shared health check configuration
- Configuring HttpClient resilience and service discovery
---
## What is ServiceDefaults?
ServiceDefaults is a shared project that provides common configuration for all services in an Aspire application:
- **OpenTelemetry** - Logging, tracing, and metrics
- **Health Checks** - Readiness and liveness endpoints
- **Service Discovery** - Automatic service resolution
- **HTTP Resilience** - Retry and circuit breaker policies
Every service references this project and calls `AddServiceDefaults()`.
---
## Project Structure
```
src/
MyApp.ServiceDefaults/
Extensions.cs
MyApp.ServiceDefaults.csproj
MyApp.Api/
Program.cs # Calls AddServiceDefaults()
MyApp.Worker/
Program.cs # Calls AddServiceDefaults()
MyApp.AppHost/
Program.cs
```
---
## ServiceDefaults Project
### Project File
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
</ItemGroup>
</Project>
```
### Extensions.cs
```csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace Microsoft.Extensions.Hosting;
public static class Extensions
{
private const string HealthEndpointPath = "/health";
private const string AlivenessEndpointPath = "/alive";
/// <summary>
/// Adds common Aspire services: OpenTelemetry, health checks,
/// service discovery, and HTTP resilience.
/// </summary>
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Resilience: retries, circuit breaker, timeouts
http.AddStandardResilienceHandler();
// Service discovery: resolve service names to addresses
http.AddServiceDiscovery();
});
return builder;
}
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
// Logging
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
// Metrics
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
// Tracing
.WithTracing(tracing =>
{
tracing
.AddSource(builder.Environment.ApplicationName)
.AddAspNetCoreInstrumentation(options =>
// Exclude health checks from traces
options.Filter = context =>
!context.Request.Path.StartsWithSegments(HealthEndpointPath) &&
!context.Request.Path.StartsWithSegments(AlivenessEndpointPath))
.AddHttpClientInstrumentation();
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
// Use OTLP exporter if endpoint is configured (Aspire Dashboard, Jaeger, etc.)
var useOtlp = !string.IsNullOrWhiteSpace(
builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlp)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
return builder;
}
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder;
}
/// <summary>
/// Maps health check endpoints. Call after UseRouting().
/// </summary>
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Only expose in development - see security note below
if (app.Environment.IsDevelopment())
{
// Readiness: all health checks must pass
app.MapHealthChecks(HealthEndpointPath);
// Liveness: only "live" tagged checks
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}
```
---
## Usage in Services
### API Service
```csharp
var builder = WebApplication.CreateBuilder(args);
// Add all service defaults
builder.AddServiceDefaults();
// Add your services
builder.Services.AddControllers();
var app = builder.Build();
// Map health endpoints
app.MapDefaultEndpoints();
app.MapControllers();
app.Run();
```
### Worker Service
```csharp
var builder = Host.CreateApplicationBuilder(args);
// Works for non-web hosts too
builder.AddServiceDefaults();
builder.Services.AddHostedService<MyWorker>();
var host = builder.Build();
host.Run();
```
---
## Adding Custom Health Checks
```csharp
public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Services.AddHealthChecks()
// Basic liveness
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
// Database readiness
.AddNpgSql(
builder.Configuration.GetConnectionString("postgres")!,
name: "postgres",
tags: ["ready"])
// Redis readiness
.AddRedis(
builder.Configuration.GetConnectionString("redis")!,
name: "redis",
tags: ["ready"])
// Custom check
.AddCheck<MyCustomHealthCheck>("custom", tags: ["ready"]);
return builder;
}
```
---
## Adding Custom Trace Sources
For Akka.NET or custom ActivitySources:
```csharp
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddSource(builder.Environment.ApplicationName)
// Akka.NET tracing
.AddSource("Akka.NET")
// Custom sources
.AddSource("MyApp.Orders")
.AddSource("MyApp.Payments")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});
return builder;
}
```
---
## Production Health Checks
For production, protect health endpoints or use different paths:
```csharp
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Always map for Kubernetes probes, but consider:
// - Using internal-only ports
// - Adding authorization
// - Rate limiting
app.MapHealthChecks("/health", new HealthCheckOptions
{
// Only return status, not details
ResponseWriter = (context, report) =>
{
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync(report.Status.ToString());
}
});
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live"),
ResponseWriter = (context, report) =>
{
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync(report.Status.ToString());
}
});
return app;
}
```
---
## Integration with AppHost
The AppHost automatically configures OTLP endpoints:
```csharp
// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("postgres");
var redis = builder.AddRedis("redis");
var api = builder.AddProject<Projects.MyApp_Api>("api")
.WithReference(postgres)
.WithReference(redis);
builder.Build().Run();
```
Services receive `OTEL_EXPORTER_OTLP_ENDPOINT` automatically, sending telemetry to the Aspire Dashboard.
---
## Best Practices
| Practice | Reason |
|----------|--------|
| **One ServiceDefaults project** | Consistent config across all services |
| **Filter health checks from traces** | Reduces noise in observability data |
| **Tag health checks** | Separate liveness from readiness |
| **Use StandardResilienceHandler** | Built-in retry, circuit breaker, timeout |
| **Add custom trace sources** | Capture domain-specific spans |
---
## Resources
- **Aspire Service Defaults**: https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/service-defaults
- **OpenTelemetry .NET**: https://opentelemetry.io/docs/languages/net/
- **Health Checks**: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks
© Aaronontheweb · MIT · Aaronontheweb/dotnet-skills