Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Resource API Patterns
description: Discover common API resource patterns in Aspire, including how to add and configure resources, use annotations, and implement custom value objects.
---

import { Aside, Steps } from '@astrojs/starlight/components';
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';

Aspire's resource model allows you to define and configure resources in a structured way, enabling seamless integration and management of your application's components. This guide provides details the common patterns for adding and configuring resources in Aspire.

Expand All @@ -30,6 +30,9 @@ An `AddX(...)` method executes:

### Signature pattern

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
public static IResourceBuilder<TResource> AddX(
this IDistributedApplicationBuilder builder,
Expand All @@ -49,46 +52,150 @@ public static IResourceBuilder<TResource> AddX(
}
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
// Resources are added via async builder methods:
const resource = await builder.addRedis("name" /*, optional params */);

// The builder handles validation, instantiation,
// and registration internally. Optional wiring:
// .withEndpoint(...)
// .withHealthCheck(...)
// .withImage(...)
// .withEnvironment(...)
// .withArgs(...)
// builder.addEventingSubscriber(...)
```

</TabItem>
</Tabs>

### Optional wiring examples

**Endpoints**:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
.WithEndpoint(port: hostPort, targetPort: containerPort, name: endpointName)
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
resource.withEndpoint({ port: hostPort, targetPort: containerPort, name: endpointName });
```

</TabItem>
</Tabs>

**Health checks**:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
.WithHealthCheck(healthCheckKey)
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
resource.withHealthCheck(healthCheckKey);
```

</TabItem>
</Tabs>

**Container images / registries**:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
.WithImage(imageName, imageTag)
.WithImageRegistry(registryUrl)
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
resource.withImage(imageName, imageTag);
resource.withImageRegistry(registryUrl);
```

</TabItem>
</Tabs>

**Entrypoint and args**:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
.WithEntrypoint("/bin/sh")
.WithArgs(context => { /* build args */ return Task.CompletedTask; })
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
resource.withEntrypoint("/bin/sh");
resource.withArgs(["--flag", "value"]);
```

</TabItem>
</Tabs>

**Environment variables**:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
.WithEnvironment(context => new("ENV_VAR", valueProvider))
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
resource.withEnvironment("ENV_VAR", value);
```

</TabItem>
</Tabs>

**Event subscriptions**:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
builder.Eventing.Subscribe<EventType>(resource, handler);
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
builder.addEventingSubscriber(async (context) => {
context.onBeforeStart(async (event) => {
// Handle event
});
});
```

</TabItem>
</Tabs>

### Summary table

| Step | Call/Method | Purpose |
Expand All @@ -104,13 +211,26 @@ builder.Eventing.Subscribe<EventType>(resource, handler);

### Signature pattern

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
public static IResourceBuilder<TResource> WithX(
this IResourceBuilder<TResource> builder,
FooOptions options) =>
builder.WithAnnotation(new FooAnnotation(options));
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

:::note
The TypeScript SDK doesn't expose `withAnnotation(...)` directly. Configuration methods like `withEndpoint(...)` and `withEnvironment(...)` handle annotation attachment internally.
:::

</TabItem>
</Tabs>

- **Target**: `IResourceBuilder<TResource>`.
- **Action**: `WithAnnotation(...)`.
- **Returns**: `IResourceBuilder<TResource>`.
Expand All @@ -128,6 +248,9 @@ Annotations are **public** metadata types implementing `IResourceAnnotation`. Th

### Definition and attachment

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
public sealed record PersistenceAnnotation(
TimeSpan? Interval,
Expand All @@ -138,6 +261,16 @@ builder.WithAnnotation(new PersistenceAnnotation(
100));
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

:::note
The TypeScript SDK doesn't expose annotations as a user-facing pattern. Resource metadata is managed internally by the SDK's configuration methods.
:::

</TabItem>
</Tabs>

### Summary table

| Concept | Pattern | Notes |
Expand Down Expand Up @@ -165,11 +298,27 @@ Custom value objects defer evaluation and allow the framework to discover depend

### Attaching to resources

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
builder.WithEnvironment(context =>
new("REDIS_CONNECTION_STRING", redis.GetConnectionStringAsync));
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript
resource.withEnvironment("REDIS_CONNECTION_STRING", redis);
```

</TabItem>
</Tabs>

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp title="Example: BicepOutputReference"
public sealed partial class BicepOutputReference :
IManifestExpressionProvider,
Expand All @@ -182,6 +331,19 @@ public sealed partial class BicepOutputReference :
}
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

:::note
In the TypeScript SDK, `BicepOutputReference` and the value provider interfaces exist as read-only types. Users consume these types (e.g., via `resource.getOutput("name")`) but don't implement custom value objects.
:::

</TabItem>
</Tabs>

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp
public static IResourceBuilder<T> WithEnvironment<T>(
this IResourceBuilder<T> builder,
Expand All @@ -194,6 +356,16 @@ public static IResourceBuilder<T> WithEnvironment<T>(
}
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

:::note
In the TypeScript SDK, `withEnvironment()` handles value binding directly without requiring custom annotation types.
:::

</TabItem>
</Tabs>

### Summary table

| Concept | Pattern | Purpose |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title: Aspire dashboard security considerations
description: Security considerations for running the Aspire dashboard
---

import { Tabs, TabItem } from '@astrojs/starlight/components';
import OsAwareTabs from '@components/OsAwareTabs.astro';

The [Aspire dashboard](/dashboard/overview/) offers powerful insights to your apps. The dashboard displays information about resources, including their configuration, console logs and in-depth telemetry.
Expand Down Expand Up @@ -82,14 +83,67 @@ The preceding Docker command:
- Configures the OTLP endpoint to use `ApiKey` authentication. This requires that incoming telemetry has a valid `x-otlp-api-key` header value.
- Configures the expected API key. `{MY_APIKEY}` in the example value should be replaced with a real API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended.

When API key authentication is configured, the dashboard validates incoming telemetry has a required API key. Apps that send the dashboard telemetry must be configured to send the API key. This can be configured in .NET with `OtlpExporterOptions.Headers`:
When API key authentication is configured, the dashboard validates incoming telemetry has a required API key. Apps that send the dashboard telemetry must be configured to send the API key. The following examples show how to configure the API key header in each language:

```csharp
<Tabs syncKey="consuming-lang">
<TabItem label="C#">

Configure the header using `OtlpExporterOptions.Headers`:

```csharp title="C# — Program.cs"
builder.Services.Configure<OtlpExporterOptions>(
o => o.Headers = $"x-otlp-api-key={MY_APIKEY}");
```

Other languages have different OpenTelemetry APIs. Passing the [`OTEL_EXPORTER_OTLP_HEADERS` environment variable](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) to apps is a universal way to configure the header.
</TabItem>
<TabItem label="Go">

Set the `OTEL_EXPORTER_OTLP_HEADERS` environment variable, or configure the header on the OTLP exporter directly:

```go title="Go — main.go"
import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"

client := otlptracegrpc.NewClient(
otlptracegrpc.WithHeaders(map[string]string{
"x-otlp-api-key": apiKey,
}),
)
```

</TabItem>
<TabItem label="Python">

Set the `OTEL_EXPORTER_OTLP_HEADERS` environment variable, or configure the header in code:

```python title="Python — app.py"
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)

exporter = OTLPSpanExporter(
headers={"x-otlp-api-key": api_key},
)
```

</TabItem>
<TabItem label="TypeScript">

Set the `OTEL_EXPORTER_OTLP_HEADERS` environment variable, or configure the header in code:

```typescript title="TypeScript — tracing.ts"
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

const exporter = new OTLPTraceExporter({
headers: {
'x-otlp-api-key': apiKey,
},
});
```

</TabItem>
</Tabs>

Passing the [`OTEL_EXPORTER_OTLP_HEADERS` environment variable](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) to apps is a universal way to configure the header across all languages.

### Telemetry API endpoint

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ The interaction service (`Aspire.Hosting.IInteractionService`) allows you to pro

This is useful for scenarios where you need to gather information from the user or provide feedback on the status of operations, regardless of how the application is being launched or deployed.

<Aside type="note">
The `IInteractionService` API is not yet available in the TypeScript AppHost SDK.
</Aside>

## The `IInteractionService` API

The `IInteractionService` interface is retrieved from the `DistributedApplication` dependency injection container. `IInteractionService` can be injected into types created from DI or resolved from `IServiceProvider`, which is usually available on a context argument passed to events.
Expand Down
Loading
Loading