Resource Hierarchies
Aspire supports modeling parent-child relationships between resources to express ownership, containment, and grouping.
Parent-child relationships serve two purposes:
- Lifecycle Containment: The child’s execution is tied to the parent’s — starting, stopping, and failures cascade from parent to child automatically.
- Dashboard Visualization: The child appears nested beneath the parent in dashboards and visualizations, improving readability.
Lifecycle Containment
Section titled “Lifecycle Containment”When a resource implements the IResourceWithParent
interface, it declares true containment — meaning its lifecycle is controlled by its parent:
- Startup: The child resource will only start after its parent starts (though readiness is independent).
- Shutdown: If the parent is stopped or removed, the child is also stopped automatically.
- Failure Propagation: If a parent enters a terminal failure state (
FailedToStart
, etc.), dependent children are stopped.
Visual Grouping (Without Lifecycle Impact)
Section titled “Visual Grouping (Without Lifecycle Impact)”Aspire also supports visual-only parent-child relationships using the WithParentRelationship()
method during resource construction.
Visual relationships:
- Affect only the dashboard layout.
- Do not affect lifecycle — the resources are independent operationally.
- Improve clarity by logically grouping related components.
Manual Relationships — No Inference
Section titled “Manual Relationships — No Inference”Aspire does not infer parent-child relationships automatically based on names, dependencies, or network links.
You must explicitly declare relationships by either:
- Implementing
IResourceWithParent
: Creates lifecycle dependency and visual nesting. - Using
.WithParentRelationship()
: Creates visual nesting only.
This explicitness ensures developers have full control over resource containment and presentation.
Real-World Scenarios
Section titled “Real-World Scenarios”The following scenarios illustrate how Aspire models parent-child relationships:
Scenario | Parent | Child |
---|---|---|
Main application container with logging sidecar | App container | Fluentd container |
Database with admin dashboard | Database container | Admin UI container |
Microservice with associated health monitor | API container | Health probe container |
Values and References
Section titled “Values and References”In Aspire, configuration, connectivity details, and dependencies between distributed resources are modeled using structured values. These values capture relationships explicitly—not just as simple strings—making the application graph portable, inspectable, and evolvable.
Aspire represents these relationships through a heterogeneous Directed Acyclic Graph (DAG). This graph tracks not only dependency ordering but also how structured values are passed between resources at multiple abstraction levels: configuration, connection, and runtime behavior.
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pg");var api = builder.AddProject("api").WithReference(db);var web = builder.AddNpmApp("web").WithReference(api);
builder.Build().Run();
architecture-beta service db(logos:postgresql)[pg] service epr(iconoir:server-connection)[Endpoint Reference] service api(logos:dotnet)[api] service ctr(iconoir:server-connection)[Connection String Reference] service frontend(logos:react)[web] db:L <-- R:ctr ctr:L <-- R:api api:L <-- R:epr epr:L <-- R:frontend
Special Case: Endpoints
Section titled “Special Case: Endpoints”Normally, resource references form an acyclic graph — no cycles allowed. However, endpoint references are treated specially and may form cycles intentionally.
Endpoints are modeled as external entities:
- They are not edges in the resource dependency graph.
- They enable realistic mutual references like:
- A frontend app and an OIDC server mutually referencing each other’s URLs (redirects, login callbacks).
- A backend exposing CORS settings that reference the frontend URL.
How the DAG Forms
Section titled “How the DAG Forms”Resources connect to each other through:
WithReference()
calls: Direct resource-to-resource dependencies.- Environment variables and CLI arguments: Configuration values containing structured references.
- Other configuration sources: Settings populated with structured value references.
Each reference adds an edge to the graph, allowing Aspire to:
- Track dependency ordering.
- Propagate structured values cleanly between services.
- Validate application integrity before execution.
Structured vs Literal Values
Section titled “Structured vs Literal Values”Aspire distinguishes between structured values and literal values.
- Structured values preserve meaning (e.g., “this is a service URL” vs. “this is a raw string”).
- Literal values are inert — they are carried unchanged across modes.
At publish time and run time:
- Structured values are either resolved (if possible) or translated into target artifacts (e.g., environment variables, argument values, etc.).
- Literal values are simply copied.
Value Providers and Deferred Evaluation
Section titled “Value Providers and Deferred Evaluation”Every structured value type in Aspire implements two fundamental interfaces:
Interface | When Used | Purpose |
---|---|---|
IValueProvider | Run mode | Resolves live values when the application starts. |
IManifestExpressionProvider | Publish mode | Emits structured expressions (like {pg.outputs.url} ) into deployment artifacts. |
This dual-interface model enables deferred evaluation:
- During publish, structured placeholders are emitted — no runtime values are resolved yet.
- During run, structured references are resolved to live values like URLs, ports, or connection strings.
Internally, value providers are attached to environment variables, CLI arguments, configuration fields, and other structured outputs during application graph construction.
Core Value Types (Expanded)
Section titled “Core Value Types (Expanded)”Type | Represents | Run Mode | Publish Mode |
---|---|---|---|
string | A literal string value. | Same literal. | Same literal. |
EndpointReference | A link to a named endpoint on another resource. | Concrete URL (http://localhost:5000 ). | Target-specific endpoint translation (DNS, ingress, etc.). |
EndpointReferenceExpression | A property of an endpoint (Host , Port , Scheme ). | Concrete value. | Platform-specific translation. |
ConnectionStringReference | A symbolic pointer to a resource’s connection string. | Concrete string. | Token or externalized secret. |
ParameterResource | An external input, secret, or setting. | Local dev value or environment lookup. | Placeholder ${PARAM} for substitution. |
ReferenceExpression | A composite string with embedded references. | Concrete formatted string. | Format string preserved for substitution. |
ReferenceExpression
Section titled “ReferenceExpression”ReferenceExpression
preserves structured value objects—endpoints, parameters, connection strings, etc.—inside an interpolated string and defers evaluation until it is safe.
Aspire evaluates the model in two distinct modes:
Phase | ReferenceExpression yields |
---|---|
Publish | Publisher-specific placeholder text (e.g., {api.bindings.http.host} ). |
Run | Concrete value such as localhost . |
Example — Using ReferenceExpression
:
var ep = api.GetEndpoint("http");
builder.WithEnvironment("HEALTH_URL", ReferenceExpression.Create( $"https://{ep.Property(EndpointProperty.Host)}:{ep.Property(EndpointProperty.Port)}/health" ));
Publish manifest excerpt:
HEALTH_URL=https://{api.bindings.http.host}:{api.bindings.http.port}/health
Run-time value:
HEALTH_URL=https://localhost:5000/health
Alternate pattern using ExecutionContext
Section titled “Alternate pattern using ExecutionContext”var ep = api.GetEndpoint("http");
if (builder.ExecutionContext.IsRunMode){ builder.WithEnvironment("HEALTH_URL", $"{ep.Url}/health"); // concrete}else{ builder.WithEnvironment("HEALTH_URL", ReferenceExpression.Create($"{ep}/health")); // structured}
Pattern used by IResourceWithConnectionString
Section titled “Pattern used by IResourceWithConnectionString”A common implementation builds the connection string with ReferenceExpression
, mixing any value objects (endpoint properties, parameters, other references):
private static ReferenceExpression BuildConnectionString( EndpointReference endpoint, ParameterResource passwordParameter){ var host = endpoint.Property(EndpointProperty.IPV4Host); var port = endpoint.Property(EndpointProperty.Port); var pwd = passwordParameter;
return ReferenceExpression.Create( $"Server={host},{port};User ID=sa;Password={pwd};TrustServerCertificate=true");}
Common errors
Section titled “Common errors”The following patterns are common mistakes when using ReferenceExpression
:
Error | Correct approach |
---|---|
Build the string first, wrap later. | Build inside ReferenceExpression.Create(...) . |
Access Endpoint.Url during publish. | Use Endpoint.Property(...) in the expression. |
Mix resolved strings and placeholders. | Keep the entire value inside one ReferenceExpression . |
Endpoint Primitives
Section titled “Endpoint Primitives”The EndpointReference
is the fundamental type used to interact with another resource’s endpoint. It provides properties such as:
Url
: The full URL of the endpoint, e.g.,http://localhost:6379
.Host
: The hostname or IP address of the endpoint.Port
: The port number of the endpoint.
These properties are dynamically resolved during the application’s startup sequence. Accessing them before the endpoints are allocated results in an exception.
IResourceWithEndpoints
Section titled “IResourceWithEndpoints”Resources supporting endpoints should implement IResourceWithEndpoints
, enabling the use of GetEndpoint(name)
to retrieve an EndpointReference
. This is implemented on the built-in ProjectResource
, ContainerResource
and ExecutableResource
. It allows endpoints to be programmatically accessed and passed between resources.
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddContainer("redis", "redis") .WithEndpoint(name: "tcp", targetPort: 6379);
// Get a reference to the "tcp" endpoint by namevar endpoint = redis.GetEndpoint("tcp");
builder.Build().Run();
What Does “Allocated” Mean?
Section titled “What Does “Allocated” Mean?”An endpoint is allocated when Aspire resolves its runtime values (e.g., Host
, Port
, Url
) during run mode. Allocation happens as part of the startup sequence, ensuring endpoints are ready for use in local development.
In publish mode, endpoints are not allocated with concrete values. Instead, their values are represented as manifest expressions or bindings (e.g., {redis.bindings.tcp.host}:{redis.bindings.tcp.port}
) that are resolved by the deployment infrastructure.
Comparison: Run Mode vs Publish Mode
Section titled “Comparison: Run Mode vs Publish Mode”Context | Run Mode | Publish Mode |
---|---|---|
Endpoint Values | Fully resolved (tcp://localhost:6379 ). | Represented by manifest expressions ({redis.bindings.tcp.url} ). |
Use Case | Local development and debugging. | Deployed environments (e.g., Kubernetes, Azure, AWS, etc.). |
Behavior | Endpoints are allocated dynamically. | Endpoint placeholders resolve at runtime. |
Use the IsAllocated
property on an EndpointReference
to check whether an endpoint has been allocated before accessing its runtime values.
Accessing Allocated Endpoints Safely
Section titled “Accessing Allocated Endpoints Safely”Endpoint resolution happens during the startup sequence of the DistributedApplication
. To safely access endpoint values (e.g., Url
, Host
, Port
), you must wait until endpoints are allocated. Aspire provides eventing APIs, such as AfterEndpointsAllocatedEvent
, to access endpoints after allocation. These APIs ensure code executes only when endpoints are ready.
var builder = DistributedApplication.CreateBuilder(args);
// Add a Redis container with a TCP endpointvar redis = builder.AddContainer("redis", "redis") .WithEndpoint(name: "tcp", targetPort: 6379);
// Retrieve the EndpointReferencevar endpoint = redis.GetEndpoint("tcp");
// Check allocation status and access UrlConsole.WriteLine($"IsAllocated: {endpoint.IsAllocated}");
try{ Console.WriteLine($"Url: {endpoint.Url}");}catch (Exception ex){ Console.WriteLine($"Error accessing Url: {ex.Message}");}
// Subscribe to AfterEndpointsAllocatedEvent for resolved propertiesbuilder.Eventing.Subscribe<AfterEndpointsAllocatedEvent>( (@event, cancellationToken) => { Console.WriteLine($"Endpoint allocated: {endpoint.IsAllocated}"); Console.WriteLine($"Resolved Url: {endpoint.Url}"); return Task.CompletedTask; });
// Start the applicationbuilder.Build().Run();
The preceding code will output different results depending on whether the application is running in run mode or publish mode:
Run Mode:
IsAllocated: TrueResolved Url: http://localhost:6379
Publish Mode:
IsAllocated: FalseError accessing Url: Endpoint has not been allocated.
Referencing Endpoints from Other Resources
Section titled “Referencing Endpoints from Other Resources”This section covers how to reference endpoints from other resources in Aspire, allowing you to wire up dependencies and configurations effectively.
Using WithReference
Section titled “Using WithReference”The WithReference
API allows you to pass an endpoint reference directly to a target resource.
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddContainer("redis", "redis") .WithEndpoint(name: "tcp", targetPort: 6379);
builder.AddProject<Projects.Worker>("worker") .WithReference(redis.GetEndpoint("tcp"));
builder.Build().Run();
WithReference
is optimized for applications that use service discovery.
Using WithEnvironment
Section titled “Using WithEnvironment”The WithEnvironment
API exposes endpoint details as environment variables, enabling runtime configuration.
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddContainer("redis", "redis") .WithEndpoint(name: "tcp", targetPort: 6379);
builder.AddProject<Worker>("worker") .WithEnvironment("RedisUrl", redis.GetEndpoint("tcp"));
builder.Build().Run();
WithEnvironment
gives full control over the configuration names injected into the target resource.
EndpointReferenceExpression
— Accessing Endpoint Parts
Section titled “EndpointReferenceExpression — Accessing Endpoint Parts”EndpointReferenceExpression
represents one field of an endpoint (Host
, Port
, Scheme
, etc.).
Call endpoint.Property(...)
to get that field; the result is still a structured value and stays deferred until publish/run time.
Need | Pattern |
---|---|
Only one part (e.g., host) | endpoint.Property(EndpointProperty.Host) |
Compose multiple parts into one setting | Build a ReferenceExpression (see dedicated section). |
var redis = builder.AddContainer("redis", "redis") .WithEndpoint("tcp", 6379);
builder.AddProject("worker") .WithEnvironment(ctx => { var ep = redis.GetEndpoint("tcp"); ctx.EnvironmentVariables["REDIS_HOST"] = ep.Property(EndpointProperty.Host); ctx.EnvironmentVariables["REDIS_PORT"] = ep.Property(EndpointProperty.Port); });
In this pattern, ep.Property(...)
returns an EndpointReferenceExpression
, which is a structured value that will be resolved at runtime.
var ep = redis.GetEndpoint("tcp");
builder.WithEnvironment("REDIS_URL", ReferenceExpression.Create( $"redis://{ep.Property(EndpointProperty.HostAndPort)}" ));
This pattern avoids resolving endpoint values prematurely and works in both publish and run modes.
EndpointProperty
API Surface
Section titled “EndpointProperty API Surface”Property | Meaning |
---|---|
Url | Fully qualified URL (scheme://host:port ). |
Host or IPV4Host | Host name or IPv4 literal. |
Port or TargetPort | Allocated host port vs. container-internal port. |
Scheme | http , tcp , etc. |
HostAndPort | Convenience composite (host:port ). |
The EndpointReference
type exposes live or placeholder values for an endpoint and provides .Property(...)
to create an EndpointReferenceExpression
.
Key members:
Member | Description |
---|---|
Url , Host , Port , Scheme , TargetPort | Concrete in run mode; undefined in publish mode. |
bool IsAllocated | Indicates if concrete values are available (run mode). |
EndpointReferenceExpression Property(EndpointProperty) | Creates a deferred expression for one field. |
EndpointReferenceExpression
implements the same IManifestExpressionProvider
/ IValueProvider
pair, so it can be embedded in a ReferenceExpression
or resolved directly with GetValueAsync()
.
Context-Based Endpoint Resolution
Section titled “Context-Based Endpoint Resolution”Aspire resolves endpoints differently based on the relationship between the source and target resources. This ensures proper communication across all environments.
Resolution Rules
Section titled “Resolution Rules”Source | Target | Resolution | Example URL |
---|---|---|---|
Container | Container | Container network (resource name:port ). | redis:6379 |
Executable / Project | Container | Host network (localhost:port ). | localhost:6379 |
Container | Executable / Project | Host network (host.docker.internal:port ). | host.docker.internal:5000 |
Advanced Scenario: Dynamic Endpoint Resolution Across Contexts
Section titled “Advanced Scenario: Dynamic Endpoint Resolution Across Contexts”Aspire resolves endpoints differently based on the execution context (e.g., run mode vs. publish mode, container vs. executable). Sometimes you want to override that resolution behavior.
Consider the following scenario, where the example shows a project that is going to setup up grafana and keycloak. We need to give the project the address for container-to-container communication between grafana and keycloak even though the target resource is a project. The project isn’t directly talking to keycloak or grafana, it’s a mediator that is just setting URLs in the appropriate configuration of each container.
Cross-Context Communication Example
Section titled “Cross-Context Communication Example”The following code demonstrates how to set environment variables for a project that needs to communicate with other resources like Grafana and Keycloak. It ensures that the URLs are correctly resolved based on the execution context (run mode vs. publish mode).
var builder = DistributedApplication.CreateBuilder(args);
var keycloak = builder.AddKeycloak("keycloak", 8080);var grafana = builder.AddContainer("grafana", "grafana/grafana");
var api = builder.AddProject<Projects.Api>("api") .WithEnvironment(ctx => { var keyCloakEndpoint = keycloak.GetEndpoint("http"); var grafanaEndpoint = grafana.GetEndpoint("http");
ctx.EnvironmentVariables["Grafana__Url"] = grafanaEndpoint;
if (ctx.ExecutionContext.IsRunMode) { // The project needs to get the URL for keycloak in the context // of the container network, but since this is a project, it'll // resolve the url in the context of the host network. We get // the runtime url and change the host and port to match the // container network pattern (host = resource name, port = target port ?? port) var keycloakUrl = new UriBuilder(keyCloakEndpoint.Url) { Host = keycloak.Resource.Name, Port = keyCloakEndpoint.TargetPort ?? keyCloakEndpoint.Port, };
ctx.EnvironmentVariables["Keycloak__AuthServerUrl"] = keycloakUrl.ToString(); } else { // In publish mode let the endpoint resolver handle the URL ctx.EnvironmentVariables["Keycloak__AuthServerUrl"] = keyCloakEndpoint; } });
builder.Build().Run();