Aspire Service Defaults: What They Do and Why They Matter

Every .NET Aspire solution ships with a project called ServiceDefaults. It looks small and unassuming, but it is doing a significant amount of heavy lifting. This shared project configures OpenTelemetry, health checks, HTTP resilience, and service discovery for every service that references it — ensuring consistent, production-ready behaviour across your entire application.

The Shape of ServiceDefaults

The ServiceDefaults project is a shared class library that exposes a single extension method: AddServiceDefaults. Every service in your Aspire solution calls it in its Program.cs:

Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

// ... register your own services

var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();

Those two calls — AddServiceDefaults and MapDefaultEndpoints — wire up a substantial amount of infrastructure.

What AddServiceDefaults Configures

If you open the Extensions.cs file inside the ServiceDefaults project, you will find it calls several methods in sequence. Let us walk through each one.

OpenTelemetry

Example.cs
public static IHostApplicationBuilder AddServiceDefaults(
    this IHostApplicationBuilder builder)
{
    builder.ConfigureOpenTelemetry();
    builder.AddDefaultHealthChecks();
    builder.Services.AddServiceDiscovery();
    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        http.AddStandardResilienceHandler();
        http.AddServiceDiscovery();
    });

    return builder;
}

The ConfigureOpenTelemetry method sets up both tracing and metrics with the OTLP exporter. It registers ASP.NET Core instrumentation, HTTP client instrumentation, and runtime metrics. The Aspire dashboard consumes this telemetry automatically — you do not need to run a separate Jaeger or Prometheus instance during development.

Health Checks

The default health check configuration adds a simple liveness check:

Example.cs
public static IHostApplicationBuilder AddDefaultHealthChecks(
    this IHostApplicationBuilder builder)
{
    builder.Services.AddHealthChecks()
        .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);

    return builder;
}

This is deliberately minimal. The idea is that each service adds its own specific health checks (database connectivity, message broker availability), while the ServiceDefaults project provides the baseline.

HTTP Resilience

The AddStandardResilienceHandler call comes from Microsoft.Extensions.Http.Resilience and configures a sensible set of Polly-based policies on every HttpClient created via the factory. This includes:

You get all of this without writing a single Polly policy yourself.

Service Discovery

The AddServiceDiscovery call enables name-based service resolution. When your frontend calls http://catalog-api/products, the service discovery system resolves that name to the actual host and port — whether running locally via the AppHost or in a production environment using DNS or configuration-based discovery.

What MapDefaultEndpoints Does

On the application side, MapDefaultEndpoints exposes the health check endpoints:

Example.cs
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
    app.MapHealthChecks("/health");
    app.MapHealthChecks("/alive", new HealthCheckOptions
    {
        Predicate = r => r.Tags.Contains("live")
    });

    return app;
}

The /health endpoint reports all registered health checks, whilst /alive reports only the liveness checks. Container orchestrators like Kubernetes use these endpoints to determine whether to restart a container or remove it from the load balancer.

Customising the Defaults

Because ServiceDefaults is just a project in your solution — not a sealed NuGet package — you can modify it freely. Common customisations include:

Adding authentication telemetry enrichment:

Example.cs
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.AddSource("MyApp.CustomActivity");
    });

Adding a database health check that all services share:

Example.cs
builder.Services.AddHealthChecks()
    .AddNpgSql(builder.Configuration.GetConnectionString("shared-db")!);

Adjusting the resilience pipeline:

Example.cs
builder.Services.ConfigureHttpClientDefaults(http =>
{
    http.AddStandardResilienceHandler(options =>
    {
        options.Retry.MaxRetryAttempts = 5;
        options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
    });
});

Why a Shared Project?

You might wonder why this is a project reference rather than a NuGet package. The answer is flexibility. Every team has slightly different requirements for telemetry, resilience, and health checks. By making ServiceDefaults a source project, Aspire gives you a well-structured starting point that you own entirely.

It is also a teaching tool. Reading through Extensions.cs shows you exactly what a production-ready .NET service should configure — and why. There is no hidden magic; just well-organised extension methods calling standard .NET APIs.

If you are adopting Aspire, the ServiceDefaults project is the first place to invest your time. Get it right, and every service you add to your solution inherits sensible, consistent behaviour from day one.