Skip to content

Resource Model

Aspire’s AppHost represents a collection of resources, known as the “resource model”. This model allows developers to define and manage the various components and services that make up their applications, providing a unified way to interact with these resources throughout the development lifecycle.

The resource model is a directed acyclic graph (DAG), where resources are nodes and dependencies are edges. This structure allows Aspire to manage complex relationships between resources, ensuring that they can be started, stopped, and monitored in a predictable manner.

A quick example that shows the basic usage:

AppHost.aspire
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pg").AddDatabase("appdata");
var api = builder.AddProject<Projects.Api>("api").WithReference(db);
var web = builder.AddNpmApp("web", "../web").WithReference(api);
builder.Build().Run();

The preceding AppHost code defines an architecture with three resources:

architecture-beta

  service db(logos:postgresql)[pq]
  service api(logos:dotnet)[api]
  service frontend(logos:react)[web]

  api:R --> L:db
  frontend:R --> L:api
  1. Use .AddXyz(...) helper methods to add and declare resources (e.g., .AddPostgres(...), .AddProject(...)).

  2. Use .WithReference(...) (or similar) to represent explicit dependencies between resources.

  3. Call Build().Run() - Aspire builds the application model (graph) and executes it handling:

    • Port allocation
    • Environment variables
    • Startup order

In Aspire, a resource is the fundamental unit for modeling your distributed applications. Resources represent services, infrastructure elements, or supporting components that together compose a distributed system.

Resources in Aspire implement the IResource interface, with most built-in resources deriving from the base Resource class.

  • Resources are inert by default — they are pure data objects that describe capabilities, configuration, and relationships. They do not manage their own lifecycle (e.g., starting, stopping, checking health). Resource lifecycle is coordinated externally by orchestrators and lifecycle hooks.
  • Resources are identified by a unique name within the application graph. This name forms the basis for referencing, wiring, and visualizing resources.

Resource metadata is expressed through annotations, which are strongly-typed objects implementing the IResourceAnnotation interface.

Annotations allow attaching additional structured information to a resource without modifying its core class. They are the primary extensibility mechanism in Aspire, enabling:

  • Core system behaviors (e.g., service discovery, connection strings, health probes).
  • Custom extensions and third-party integrations.
  • Layering of optional capabilities without inheritance or tight coupling.

For additional information on annotations, see Resource API Patterns: Annotations.

Resources are typically added using fluent extension methods such as .AddRedis(...), .AddProject(...), or .AddPostgres(...). Extension methods encapsulate:

  • Construction of the resource object.
  • Attachment of annotations that describe defaults, discovery hints, or runtime behavior.
  • Relationships like wiring up dependencies (e.g., via .WithReference(...)).

This pattern improves the developer experience by:

  • Setting sane defaults automatically.
  • Making required configuration obvious and discoverable.
  • Providing a product-like feel to adding infrastructure.

To continue from the previous example, here’s how you can add resources and wire them together in the AppHost:

AppHost.aspire
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pg").AddDatabase("appdata");
var api = builder.AddProject<Projects.Api>("api").WithReference(db);
var web = builder.AddNpmApp("web", "../web").WithReference(api);
builder.Build().Run();

The preceding example:

  • A PostgreSQL server (pg) is created and configured with a database named appdata.
  • A backend service (api) is created and connected to the database.
  • A frontend app (web) is created and reverse-proxies traffic to the backend.

Each resource participates in the application graph passively, with dependencies expressed through references.

Resources describe capabilities rather than controlling them directly. Annotations provide a rich, extensible metadata system for resources, while fluent extension methods guide developers toward correct and complete configurations. Names serve as the identity anchors for wiring and dependency resolution throughout the application graph.

In Aspire, many common infrastructure and application patterns are available as built-in resource types. Built-in resources simplify modeling real-world systems by providing ready-made building blocks that automatically integrate with the Aspire runtime, lifecycle management, health tracking, and dashboard visualization.

Built-in resources:

  • Handle lifecycle transitions automatically.
  • Raise lifecycle events (like startup and readiness signals).
  • Push status updates to the system for real-time orchestration and monitoring.
  • Expose endpoints, environment variables, and metadata needed for dependent resources.

They help developers express distributed applications consistently without needing to manually orchestrate startup, shutdown, and dependency wiring.

All resources in Aspire begin in an Unknown state when added to the application graph. This ensures that the resource graph can be fully constructed before any execution, dependency resolution, or publishing occurs.

StateMeaning
ExitedCompleted execution (typically for short-lived jobs, migrations, one-shot tasks).
FailedToStartFailed during startup initialization.
FinishedRan to successful completion (used for batch workloads or scripts).
HiddenPresent in the model but intentionally hidden from dashboard UI (e.g., infrastructure helpers).
NotStartedDefined but not yet scheduled to start.
RunningSuccessfully started; may have separate application-level health probing.
RuntimeUnhealthyThe container or host runtime environment (e.g., Docker daemon) is unavailable, preventing start-up.
StartingActively starting; readiness not yet confirmed.
StoppingResource is shutting down gracefully.
UnknownDefault state when first added to the graph. No execution planned yet.
TerminalStatesList of terminal states (e.g., Finished, Exited, FailedToStart).
WaitingAwaiting dependencies to become ready (e.g., using .WaitFor(...)).

Resource states drive:

  • Readiness checks to unblock dependent resources.
  • Dashboard visualization and state coloring.
  • Orchestration sequencing for startup and shutdown.
  • Health monitoring at runtime.

Aspire provides a set of fundamental built-in resource types that serve as the foundation for modeling execution units:

TypePurpose
ContainerResourceRuns Docker containers as resources.
ExecutableResourceLaunches arbitrary executables or scripts as resources.
ProjectResourceRuns a .NET project directly (build + launch workflow).
ParameterResourceRepresents a parameter or configuration value.

These types are infrastructure-oriented primitives. They model how code and applications are packaged and executed.

Built-in types:

  • Automatically participate in resource orchestration.
  • Raise standard lifecycle events without manual intervention.
  • Report health and readiness status.
  • Expose connection endpoints for dependent services.

Custom resources must opt-in manually to these behaviors.

Aspire defines standard events to orchestrate resource lifecycles, in the following order:

  1. InitializeResourceEvent: Fired when a resource is first created to kick off the resource’s lifecycle.
  2. ConnectionStringAvailableEvent: Fired when a connection string is ready, enabling dependent resources to wire themselves dynamically based on the resource’s outputs.
  3. ResourceEndpointsAllocatedEvent: Fired when endpoints have been allocated and can be evaluated successfully.
  4. BeforeResourceStartedEvent: Fired just before the resource starts executing as a last-chance dynamic setup or validation point.
  5. ResourceReadyEvent: Fired when the resource is considered “ready,” unblocking any dependents waiting for the resource.

Lifecycle events allow:

  • Dynamic reconfiguration just before startup.
  • Dependent resource activation based on readiness.
  • Wiring services together based on runtime-generated outputs.

Beyond events, Aspire uses asynchronous state snapshots to report resource status continuously.

  • ResourceNotificationService handles snapshot updates.
  • Status updates involve:
    1. Receiving the previous immutable snapshot.
    2. Mutating to a new snapshot representing the updated state.
    3. Publishing the new snapshot to the dashboard and orchestrators.

Snapshots:

  • Always reflect the latest known status.
  • Are non-blocking and do not delay orchestration.
  • Drive dashboard visualization and orchestration decisions.

Aspire integrates with .NET health checks to monitor the status of resources after they have started. The health check mechanism is tied into the resource lifecycle:

  1. When a resource transitions to the Running state, Aspire checks if it has any associated health check annotations (typically added via .WithHealthCheck(...)).
  2. If health checks are configured: Aspire begins executing these checks periodically. The resource is considered fully “ready” only after its health checks pass successfully. Once healthy, Aspire automatically publishes the ResourceReadyEvent.
  3. If no health checks are configured: The resource is considered “ready” as soon as it enters the Running state. Aspire automatically publishes the ResourceReadyEvent immediately in this case.

This automatic handling ensures that dependent resources (using mechanisms like .WaitFor(...)) only proceed when the target resource is truly ready, either by simply running or by passing its defined health checks.

Aspire supports logging output on a per-resource basis, which is displayed in the console window and can be surfaced in the dashboard. This log stream is especially useful for monitoring what a resource is doing in real time.

For built-in resources, Aspire captures and forwards output from:

  • stdout and stderr of containers (e.g., Docker).
  • Process output from executables or .NET projects.

For custom resources, developers can write directly to a resource’s log using the ResourceLoggerService.

This service provides an ILogger scoped to the individual resource instance, enabling human-readable, contextual logging.

var logger = resourceLoggerService.GetLogger(myResource);
logger.LogInformation("Starting provisioning…");

The following APIs are likely to be used when working with resource logging:

APIDescription
ResourceLoggerService.GetLogger(IResource)Returns a scoped ILogger.
ResourceLoggerService.WatchAsyncStream log lines.

Resource logs are designed to be human-readable and provide insights into the resource’s behavior, state changes, and any issues encountered during execution. Use the ResourceNotificationService to publish structured state changes.

Aspire defines a set of optional standard interfaces that allow resources to declare their capabilities in a structured, discoverable way. Implementing these interfaces enables dynamic wiring, publishing, service discovery, and orchestration without hardcoded type knowledge.

These interfaces are the foundation for Aspire’s polymorphic behaviors — enabling tools, publishers, and the runtime to treat resources uniformly based on what they can do, rather than what they are.

  • Dynamic discovery: Tooling and runtime systems can automatically adapt based on resource capabilities.
  • Loose coupling: Behaviors (like environment wiring, service discovery, or connection sharing) are opt-in.
  • Extensibility: New resource types can integrate seamlessly into the Aspire ecosystem by implementing one or more interfaces.
InterfacePurpose
IResourceWithArgsSupplies additional CLI arguments when launching a project or executable.
IResourceWithConnectionStringProvides a connection string output for consumers to connect to the resource.
IResourceWithEndpointsExposes ports, URLs, or connection points that other resources can consume.
IResourceWithEnvironmentSupports setting environment variables for the resource.
IResourceWithServiceDiscoveryRegisters a service hostname and metadata for discovery by other resources.
IResourceWithWaitSupportThis resource can wait for other resources.
IResourceWithWithoutLifetimeThis resource does not have a lifecycle. (e.g. connection string, parameter)

IResourceWithEnvironment

builder.WithEnvironment("MY_SETTING", "value");

Allows setting environment variables that are passed to the resource when it starts.

IResourceWithServiceDiscovery

builder.WithReference(myResourceWithDiscovery);

Exposes the resource via DNS-style service discovery. Downstream resources can refer to it by logical name.

IResourceWithEndpoints

builder.GetEndpoint("http");

When a resource implements IResourceWithEndpoints, it allows referencing specific endpoints (e.g., http, tcp) for reverse proxies or connection targets.

IResourceWithConnectionString

builder.WithReference(myDatabaseResource);

Allows wiring a database connection string into environment variables, configurations, or CLI arguments.

IResourceWithArgs

builder.WithArgs("2", "--url", endpoint);

Allows setting command-line arguments on the resource.

IResourceWithWaitSupport

builder.WaitFor(otherResource)

This resource can wait on other resources. A ParameterResource is an example of resources that cannot wait.

By modeling behaviors through interfaces rather than concrete types, Aspire enables:

  • Tooling flexibility: Publishers can wire environment variables, endpoints, and arguments generically.
  • Runtime uniformity: Dashboards and orchestrators treat resources based on capabilities, not type-specific logic.
  • Ecosystem extensibility: New resource types can plug into the system without modifying core code.

Interfaces allow Aspire to remain open, flexible, and adaptable as new types of services, platforms, and deployment targets emerge.

Ask & Answer Collaborate Community Discuss Watch