Skip to content
Merged
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 @@ -1243,10 +1243,14 @@ When the command runs in the dashboard or CLI, `dotnet --version` (C#) or `node

### Dynamic process command

When the command arguments depend on runtime context — for example, a dataset name supplied by the user through the dashboard's argument dialog — use the callback overload (C# only) to build the `ProcessCommandSpec` dynamically:
When the command arguments depend on runtime context — for example, a dataset name supplied by the user through the dashboard's argument dialog — build the process specification from the command execution context:

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

```csharp title="AppHost.cs"
#pragma warning disable ASPIREPROCESSCOMMAND001
#pragma warning disable ASPIREINTERACTION001

using Aspire.Hosting.ApplicationModel;

Expand All @@ -1268,16 +1272,73 @@ builder.AddRedis("cache")
],
EnvironmentVariables = { ["ConnectionStrings__db"] = "Host=localhost;Database=db" },
},
options: new ProcessCommandOptions { MaxOutputLineCount = 20 });
options: new ProcessCommandOptions
{
MaxOutputLineCount = 20,
Arguments =
[
new InteractionInput { Name = "dataset", Label = "Dataset", InputType = InputType.Text, Required = true },
],
});

builder.Build().Run();
```

The `context` parameter is an `ExecuteCommandContext`, so you can read user-supplied arguments via `context.Arguments.GetString(name)`, resolve services, and check the cancellation token.
</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript title="apphost.ts"
import {
createBuilder,
type ExecuteCommandContext,
InputType,
} from './.modules/aspire.js';

const builder = await createBuilder();

const cache = await builder.addRedis('cache');

await cache.withProcessCommandFactory(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, what are the rules for when to append Factory to the end of the polygot method name?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have, calback and factory, this just seemed like a better name, but maybe we should call it Callback.

Sébastien Ros (@sebastienros)

'seed-data',
'Seed data',
async (context: ExecuteCommandContext) => {
const args = await context.arguments();
const dataset = await args.requiredValue('dataset');

return {
executablePath: 'node',
arguments: ['./scripts/seed-data.js', '--dataset', dataset],
environmentVariables: {
NODE_ENV: 'development',
},
};
},
{
commandOptions: {
arguments: [
{
name: 'dataset',
label: 'Dataset',
inputType: InputType.Text,
required: true,
},
],
},
maxOutputLineCount: 20,
}
);

await builder.build().run();
```

</TabItem>
</Tabs>

In C#, the callback overload receives an `ExecuteCommandContext`. In TypeScript, `withProcessCommandFactory` receives the same execution context and returns the process specification. The dashboard renders the configured arguments as a prompt dialog before starting the process, and the entered values are available through the command context.

### `ProcessCommandSpec` properties

The following configuration options are available for the process to run. In C#, these map to `ProcessCommandSpec` properties. In TypeScript, they are provided as fields in the options object passed to `withProcessCommand`.
The following configuration options are available for the process to run. In C#, these map to `ProcessCommandSpec` properties. In TypeScript, provide them as fields in the options object passed to `withProcessCommand`, or return them from the `withProcessCommandFactory` callback.

- **Executable path** — the path to the process to launch. Short names (no directory separator) are resolved from the AppHost's `PATH`.
- **Arguments** — a list of arguments passed to the process. Each entry is treated as a separate argument, so no shell quoting or escaping is needed.
Expand All @@ -1289,7 +1350,7 @@ The following configuration options are available for the process to run. In C#,

### `ProcessCommandOptions`

The following options control how `WithProcessCommand` handles the process result. In C#, they are set on `ProcessCommandOptions`. In TypeScript, they are fields in the same options object as the process configuration above.
The following options control how `WithProcessCommand` handles the process result. In C#, set them on `ProcessCommandOptions`. In TypeScript, provide them in the same options object as the process configuration for `withProcessCommand`, or in the fourth argument to `withProcessCommandFactory`.

- **Max output line count** — the maximum number of combined stdout/stderr lines captured and returned as the command result. Defaults to 50. Lines beyond this limit are silently discarded (oldest lines first).
- **Display immediately** — when `true` (the default), the captured output is automatically shown in the dashboard as soon as the command finishes.
Expand All @@ -1303,45 +1364,3 @@ The following options control how `WithProcessCommand` handles the process resul
2. Otherwise, the AppHost process's `PATH` is searched for the named executable. On Windows, `PATHEXT` extensions are also tried.

This means you can reference tools such as `dotnet`, `node`, or `docker` by short name as long as they are on the AppHost's PATH when the app starts.

### Combining with command arguments

`WithProcessCommand` uses `WithCommand` internally. In C# dynamic process commands, you can combine it with the [Command arguments](#command-arguments) feature to prompt the user for input before the process runs:

```csharp title="AppHost.cs"
#pragma warning disable ASPIREPROCESSCOMMAND001
#pragma warning disable ASPIREINTERACTION001

using Aspire.Hosting.ApplicationModel;

var builder = DistributedApplication.CreateBuilder(args);

builder.AddPostgres("postgres")
.WithProcessCommand(
name: "run-migration",
displayName: "Run migration",
createProcessSpec: context => new ProcessCommandSpec("dotnet")
{
Arguments =
[
"run",
"--project",
"tools/Migrations",
"--",
"--target",
context.Arguments.GetString("target") ?? "latest",
],
},
options: new ProcessCommandOptions
{
MaxOutputLineCount = 50,
Arguments =
[
new InteractionInput { Name = "target", Label = "Target migration", InputType = InputType.Text },
],
});

builder.Build().Run();
```

The dashboard renders the `Arguments` as a prompt dialog before starting the process, and the entered values are accessible via `context.Arguments`.
Loading