From d6ced0c12d78bba285cc484c94e933be2a4dc374 Mon Sep 17 00:00:00 2001 From: AJ Matthews Date: Wed, 13 May 2026 15:51:33 +0100 Subject: [PATCH 1/6] Added TypeScript, Go, and Python example code. --- .../architecture/resource-api-patterns.mdx | 158 +++++++++- .../dashboard/security-considerations.mdx | 60 +++- .../extensibility/interaction-service.mdx | 106 ++++++- .../docs/fundamentals/external-parameters.mdx | 294 +++++++++++++++++- .../docs/fundamentals/health-checks.mdx | 288 ++++++++++++++++- .../fundamentals/persist-data-volumes.mdx | 151 ++++++++- .../docs/fundamentals/service-discovery.mdx | 249 ++++++++++++++- 7 files changed, 1291 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx b/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx index f1cb1745d..061441c07 100644 --- a/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx +++ b/src/frontend/src/content/docs/architecture/resource-api-patterns.mdx @@ -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. @@ -30,6 +30,9 @@ An `AddX(...)` method executes: ### Signature pattern + + + ```csharp public static IResourceBuilder AddX( this IDistributedApplicationBuilder builder, @@ -49,46 +52,134 @@ public static IResourceBuilder AddX( } ``` + + + +:::note +In the TypeScript SDK, resources are added using `async` builder methods like `builder.addRedis("name")`. The TypeScript SDK doesn't use extension methods or generics — resource creation methods are built into the builder object. +::: + + + + ### Optional wiring examples **Endpoints**: + + + ```csharp .WithEndpoint(port: hostPort, targetPort: containerPort, name: endpointName) ``` + + + +:::note +In the TypeScript SDK, use `.withEndpoint({ port, targetPort, name })` with an options object instead of named parameters. +::: + + + + **Health checks**: + + + ```csharp .WithHealthCheck(healthCheckKey) ``` + + + +:::note +In the TypeScript SDK, use `.withHealthCheck("key")` with camelCase method naming. +::: + + + + **Container images / registries**: + + + ```csharp .WithImage(imageName, imageTag) .WithImageRegistry(registryUrl) ``` + + + +:::note +In the TypeScript SDK, container image configuration is passed directly to methods like `builder.addContainer("name", "image:tag")`. +::: + + + + **Entrypoint and args**: + + + ```csharp .WithEntrypoint("/bin/sh") .WithArgs(context => { /* build args */ return Task.CompletedTask; }) ``` + + + +:::note +The `withEntrypoint` and `withArgs` APIs are not yet available in the TypeScript AppHost SDK. +::: + + + + **Environment variables**: + + + ```csharp .WithEnvironment(context => new("ENV_VAR", valueProvider)) ``` + + + +:::note +In the TypeScript SDK, use `.withEnvironment("ENV_VAR", value)` with camelCase method naming. +::: + + + + **Event subscriptions**: + + + ```csharp builder.Eventing.Subscribe(resource, handler); ``` + + + +:::note +The eventing subscription API is not yet available in the TypeScript AppHost SDK. +::: + + + + ### Summary table | Step | Call/Method | Purpose | @@ -104,6 +195,9 @@ builder.Eventing.Subscribe(resource, handler); ### Signature pattern + + + ```csharp public static IResourceBuilder WithX( this IResourceBuilder builder, @@ -111,6 +205,16 @@ public static IResourceBuilder WithX( builder.WithAnnotation(new FooAnnotation(options)); ``` + + + +:::note +In the TypeScript SDK, configuration methods use camelCase naming (e.g., `.withAnnotation(...)`) and accept options objects rather than using C# extension methods and generics. +::: + + + + - **Target**: `IResourceBuilder`. - **Action**: `WithAnnotation(...)`. - **Returns**: `IResourceBuilder`. @@ -128,6 +232,9 @@ Annotations are **public** metadata types implementing `IResourceAnnotation`. Th ### Definition and attachment + + + ```csharp public sealed record PersistenceAnnotation( TimeSpan? Interval, @@ -138,6 +245,16 @@ builder.WithAnnotation(new PersistenceAnnotation( 100)); ``` + + + +:::note +The TypeScript SDK doesn't use annotations or the `IResourceAnnotation` pattern. Resource metadata is managed through the TypeScript SDK's own configuration model. +::: + + + + ### Summary table | Concept | Pattern | Notes | @@ -165,11 +282,27 @@ Custom value objects defer evaluation and allow the framework to discover depend ### Attaching to resources + + + ```csharp builder.WithEnvironment(context => new("REDIS_CONNECTION_STRING", redis.GetConnectionStringAsync)); ``` + + + +:::note +In the TypeScript SDK, use `.withEnvironment("NAME", value)` where values are resolved by the runtime. +::: + + + + + + + ```csharp title="Example: BicepOutputReference" public sealed partial class BicepOutputReference : IManifestExpressionProvider, @@ -182,6 +315,19 @@ public sealed partial class BicepOutputReference : } ``` + + + +:::note +The TypeScript SDK doesn't use the `IManifestExpressionProvider`, `IValueProvider`, or `IValueWithReferences` interfaces. Value resolution is handled internally by the TypeScript runtime. +::: + + + + + + + ```csharp public static IResourceBuilder WithEnvironment( this IResourceBuilder builder, @@ -194,6 +340,16 @@ public static IResourceBuilder WithEnvironment( } ``` + + + +:::note +In the TypeScript SDK, environment variable binding is handled directly by the `.withEnvironment()` method without requiring custom annotation types. +::: + + + + ### Summary table | Concept | Pattern | Purpose | diff --git a/src/frontend/src/content/docs/dashboard/security-considerations.mdx b/src/frontend/src/content/docs/dashboard/security-considerations.mdx index 46e9937b5..626b798f2 100644 --- a/src/frontend/src/content/docs/dashboard/security-considerations.mdx +++ b/src/frontend/src/content/docs/dashboard/security-considerations.mdx @@ -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. @@ -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 + + + +Configure the header using `OtlpExporterOptions.Headers`: + +```csharp title="C# — Program.cs" builder.Services.Configure( 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. + + + +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, + }), +) +``` + + + + +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}, +) +``` + + + + +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, + }, +}); +``` + + + + +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 diff --git a/src/frontend/src/content/docs/extensibility/interaction-service.mdx b/src/frontend/src/content/docs/extensibility/interaction-service.mdx index 37e53fba8..3775ad2b3 100644 --- a/src/frontend/src/content/docs/extensibility/interaction-service.mdx +++ b/src/frontend/src/content/docs/extensibility/interaction-service.mdx @@ -3,7 +3,7 @@ title: Interaction service (Preview) description: Use the interaction service API to prompt users for input, request confirmation, and display messages in the Aspire dashboard or CLI during publish and deploy. --- -import { Aside, Steps } from '@astrojs/starlight/components'; +import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; import { Image } from 'astro:assets'; import messageDialog from '@assets/extensibility/interaction-service-message-dialog.png'; import messageBar from '@assets/extensibility/interaction-service-message-bar.png'; @@ -25,6 +25,9 @@ The `IInteractionService` interface is retrieved from the `DistributedApplicatio When you request `IInteractionService`, be sure to check if it's available for usage. If you attempt to use the interaction service when it's not available (`IInteractionService.IsAvailable` returns `false`), an exception is thrown. + + + ```csharp var interactionService = serviceProvider.GetRequiredService(); if (interactionService.IsAvailable) @@ -40,6 +43,16 @@ if (interactionService.IsAvailable) } ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + The interaction service has several methods that you use to interact with users or display messages. The behavior of these methods depends on the execution context: - **Dashboard context** (`aspire run` or direct AppHost launch): Interactions appear as modal dialogs, notifications, and form inputs in the [Aspire dashboard web interface](/dashboard/overview/). @@ -76,6 +89,9 @@ This article demonstrates the interaction service in the context of a `WithComma For example: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -90,6 +106,16 @@ builder.AddFakeResource("fake-resource") }); ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + For CLI specific contexts, the interaction service is retrieved from either the `PublishingContext` or `PipelineStepContext` depending on the operation being performed. @@ -110,6 +136,9 @@ Dialog messages provide important information that requires user attention. The `IInteractionService.PromptMessageBoxAsync` method displays a message with customizable response options. + + + ```csharp title="AppHost.cs" var interactionService = context.ServiceProvider.GetRequiredService(); var result = await interactionService.PromptMessageBoxAsync( @@ -139,6 +168,16 @@ return result.Data : CommandResults.Failure("The user doesn't like the example"); ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + **Dashboard view:** Aspire dashboard interface showing a message dialog with a title, message, and buttons. @@ -157,6 +196,9 @@ In the dashboard, notification messages appear stacked at the top, so you can sh The `PromptNotificationAsync` method displays informational messages with optional action links in the dashboard context. You don't have to await the result of a notification message if you don't need to. This is especially useful for notifications, since you might want to display a notification and continue without waiting for user to dismiss it. + + + ```csharp title="AppHost.cs" var interactionService = context.ServiceProvider.GetRequiredService(); // Demonstrating various notification types with different intents @@ -212,6 +254,16 @@ await Task.WhenAll(tasks); return CommandResults.Success(); ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + The previous example demonstrates several ways to use the notification API. Each approach displays different types of notifications, all of which are invoked in parallel and displayed in the dashboard around the same time. **Dashboard view:** @@ -230,6 +282,9 @@ Use the interaction service when you need the user to confirm an action before p For operations that can't be undone, such as deleting resources, always prompt for confirmation: + + + ```csharp title="AppHost.cs" var interactionService = context.ServiceProvider.GetRequiredService(); // Prompt for confirmation before resetting database @@ -257,6 +312,16 @@ else } ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + **Dashboard view:** Aspire dashboard interface showing a confirmation dialog with a title, message, and buttons for confirming or canceling the operation. @@ -287,6 +352,9 @@ It's possible to create wizard-like flows using the interaction service. By chai Consider the following example, which prompts the user for multiple input values: + + + ```csharp title="AppHost.cs" var interactionService = context.ServiceProvider.GetRequiredService(); var loggerService = context.ServiceProvider.GetRequiredService(); @@ -358,6 +426,16 @@ else } ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + **Dashboard view:** This renders on the dashboard as shown in the following image: @@ -419,6 +497,9 @@ To configure a dynamic input, set the `InteractionInput.DynamicLoading` property - **DependsOnInputs**: A list of input names that the dynamic input depends on. When any of these inputs change, the `LoadCallback` is triggered to refresh the options for the dynamic input. If no dependencies are specified then the callback is triggered when the interaction starts. - **AlwaysLoadOnStart**: If set to `true`, the `LoadCallback` is always called when the interaction starts, even if the dynamic input has dependencies. + + + ```csharp var inputs = new List { @@ -466,6 +547,16 @@ if (!result.Canceled) } ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + In the preceding example, the `DatabaseVersion` input is dynamically populated based on the selected `DatabaseType`. #### Input validation @@ -474,6 +565,9 @@ Basic input validation is available by configuring `InteractionInput`. It provid For complex scenarios, you can provide custom validation logic using the `InputsDialogInteractionOptions.ValidationCallback` property: + + + ```csharp // Multiple inputs with custom validation var databaseInputs = new List @@ -543,6 +637,16 @@ if (!dbResult.Canceled && dbResult.Data != null) } ``` + + + +:::note +The `IInteractionService` API is not yet available in the TypeScript AppHost SDK. +::: + + + + Prompting the user for a password and confirming they match is referred to as "dual independent verification" input. This approach is common in scenarios where you want to ensure the user enters the same password twice to avoid typos or mismatches. ### Best practices for user input diff --git a/src/frontend/src/content/docs/fundamentals/external-parameters.mdx b/src/frontend/src/content/docs/fundamentals/external-parameters.mdx index 3ea783d04..fcea9465a 100644 --- a/src/frontend/src/content/docs/fundamentals/external-parameters.mdx +++ b/src/frontend/src/content/docs/fundamentals/external-parameters.mdx @@ -3,7 +3,7 @@ title: External parameters description: Learn how to express parameters such as secrets, connection strings, and other configuration values that might vary between environments. --- -import { Aside, Steps } from '@astrojs/starlight/components'; +import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; import { Image } from 'astro:assets'; import dashboardUnresolvedParams from '@assets/fundamentals/dashboard-unresolved-parameters-message.png'; import customizedParameterUi from '@assets/fundamentals/customized-parameter-ui.png'; @@ -16,6 +16,9 @@ Parameter values are read from the `Parameters` section of the AppHost's configu Consider the following example AppHost _AppHost.cs_ file: + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -26,6 +29,26 @@ builder.AddProject("api") .WithEnvironment("ENVIRONMENT_VARIABLE_NAME", parameter); ``` + + + +```typescript title="TypeScript — apphost.ts" twoslash +import { createBuilder } from './.modules/aspire.js'; + +const builder = await createBuilder(); + +// Add a parameter named "example-parameter-name" +const parameter = await builder.addParameter("example-parameter-name"); + +await builder.addProject("api", "./ApiService/ApiService.csproj") + .withEnvironment("ENVIRONMENT_VARIABLE_NAME", parameter); + +await builder.build().run(); +``` + + + + The preceding code adds a parameter named `example-parameter-name` to the AppHost. The parameter is then passed to the `Projects.ApiService` project as an environment variable named `ENVIRONMENT_VARIABLE_NAME`. ### Configure parameter values @@ -44,6 +67,9 @@ Consider the following AppHost configuration file _appsettings.json_: The preceding JSON configures a parameter in the `Parameters` section of the AppHost configuration. In other words, that AppHost is able to find the parameter as it's configured. For example, you could walk up to the `IDistributedApplicationBuilder.Configuration` and access the value using the `Parameters:example-parameter-name` key: + + + ```csharp var builder = DistributedApplication.CreateBuilder(args); @@ -51,6 +77,21 @@ var key = $"Parameters:example-parameter-name"; var value = builder.Configuration[key]; // value = "local-value" ``` + + + +```typescript title="TypeScript — apphost.ts" twoslash +import { createBuilder } from './.modules/aspire.js'; + +const builder = await createBuilder(); + +const key = "Parameters:example-parameter-name"; +// Configuration access is handled internally by the Aspire runtime +``` + + + +