From 409c87909399fb9653b8bc058f78013ef3f1738e Mon Sep 17 00:00:00 2001 From: "aspire-repo-bot[bot]" <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:15:16 +0000 Subject: [PATCH 1/3] docs: document WithExplicitStart() callback deferral behavior Add a new 'Defer resource start with explicit start' section to resource-lifetimes.mdx explaining how WithExplicitStart() interacts with execution configuration callbacks (WithEnvironment, WithArgs): - Session-scoped explicit-start resources defer DCP registration until manual start, so callbacks run only when the user starts the resource from the dashboard. - Persistent explicit-start resources are registered immediately (to detect existing instances), but use a Spec.Start patch on manual start so callbacks are not re-evaluated a second time. Documents microsoft/aspire#17825. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../docs/app-host/resource-lifetimes.mdx | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx index 3335e6aa7..02be9321a 100644 --- a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx +++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx @@ -1,6 +1,6 @@ --- title: Configure resource lifetimes in Aspire -description: Learn how session, persistent, resource-scoped, and parent-process lifetimes control Aspire containers, executables, and projects. +description: Learn how session, persistent, resource-scoped, and parent-process lifetimes and explicit start control Aspire containers, executables, and projects. --- import { Tabs, TabItem } from '@astrojs/starlight/components'; @@ -68,6 +68,52 @@ Use resource-scoped lifetime when a companion resource should follow the lifetim Aspire evaluates the source resource's lifetime when it prepares the application model, so later lifetime changes to the source resource are reflected by the dependent resource. The source and dependent resources must both support lifetime configuration. +## Defer resource start with explicit start + +Use `WithExplicitStart()` to prevent a resource from starting automatically with the rest of the AppHost. The resource appears in the dashboard but remains stopped until you start it manually. + +This is useful for resources that require user interaction before they can start — for example, resources that use dynamic configuration callbacks to prompt for credentials or other runtime input. Combining `WithExplicitStart()` with callback-based configuration APIs like `WithEnvironment` or `WithArgs` gives you precise control over when those callbacks run. + +### Session-scoped explicit-start resources + +For session-scoped resources (the default lifetime), Aspire defers DCP registration until you manually start the resource from the dashboard. This means execution configuration callbacks — such as `WithEnvironment(context => ...)` — are **not** evaluated during AppHost startup. They run only when the resource is manually started. + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +// The callback below is NOT evaluated during AppHost startup. +// It runs only when the resource is manually started from the dashboard. +var job = builder.AddExecutable("batch-job", "dotnet", ".", "run", "--project", "BatchJob") + .WithExplicitStart() + .WithEnvironment(async context => + { + // Prompt or compute dynamic configuration at start time + context.EnvironmentVariables["API_KEY"] = await GetApiKeyAsync(); + }); + +builder.Build().Run(); +``` + +### Persistent explicit-start resources + +For persistent resources, Aspire must register the resource with DCP immediately at startup so it can discover any existing running instance. However, when you manually start a persistent explicit-start resource, Aspire patches the existing DCP resource to start it rather than deleting and recreating it. This means the execution configuration callbacks run once during startup registration and are **not** re-evaluated when you manually start the resource. + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +// Persistent explicit-start resources are registered at startup to detect existing instances. +// The callback runs during startup registration — not again when manually started. +var cache = builder.AddContainer("long-lived-cache", "my-cache-image") + .WithPersistentLifetime() + .WithExplicitStart() + .WithEnvironment(context => + { + context.EnvironmentVariables["CACHE_SIZE"] = "512mb"; + }); + +builder.Build().Run(); +``` + ## Configure a persistent container For new code, configure a persistent container with `WithPersistentLifetime()`: From 25afa91c452cc3265e3414e752fa4832a54f75a0 Mon Sep 17 00:00:00 2001 From: David Pine Date: Mon, 27 Jul 2026 14:29:42 -0500 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/content/docs/app-host/resource-lifetimes.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx index 02be9321a..3812b752f 100644 --- a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx +++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx @@ -1,6 +1,6 @@ --- title: Configure resource lifetimes in Aspire -description: Learn how session, persistent, resource-scoped, and parent-process lifetimes and explicit start control Aspire containers, executables, and projects. +description: Learn how session, persistent, resource-scoped, and parent-process lifetimes, plus explicit start, control Aspire containers, executables, and projects. --- import { Tabs, TabItem } from '@astrojs/starlight/components'; @@ -72,11 +72,11 @@ Aspire evaluates the source resource's lifetime when it prepares the application Use `WithExplicitStart()` to prevent a resource from starting automatically with the rest of the AppHost. The resource appears in the dashboard but remains stopped until you start it manually. -This is useful for resources that require user interaction before they can start — for example, resources that use dynamic configuration callbacks to prompt for credentials or other runtime input. Combining `WithExplicitStart()` with callback-based configuration APIs like `WithEnvironment` or `WithArgs` gives you precise control over when those callbacks run. +Starting in Aspire 13.5, `WithExplicitStart()` also affects when execution configuration callbacks (for example, `WithEnvironment` and `WithArgs`) are evaluated, depending on whether the resource is session-scoped or persistent. ### Session-scoped explicit-start resources -For session-scoped resources (the default lifetime), Aspire defers DCP registration until you manually start the resource from the dashboard. This means execution configuration callbacks — such as `WithEnvironment(context => ...)` — are **not** evaluated during AppHost startup. They run only when the resource is manually started. +For session-scoped resources (the default lifetime), Aspire defers Developer Control Plane (DCP) registration until you manually start the resource from the dashboard. This means execution configuration callbacks — such as `WithEnvironment(context => ...)` — are **not** evaluated during AppHost startup. They run only when the resource is manually started. ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -96,7 +96,7 @@ builder.Build().Run(); ### Persistent explicit-start resources -For persistent resources, Aspire must register the resource with DCP immediately at startup so it can discover any existing running instance. However, when you manually start a persistent explicit-start resource, Aspire patches the existing DCP resource to start it rather than deleting and recreating it. This means the execution configuration callbacks run once during startup registration and are **not** re-evaluated when you manually start the resource. +For persistent resources, Aspire must register the resource with the Developer Control Plane (DCP) immediately at startup so it can discover any existing running instance. However, when you manually start a persistent explicit-start resource, Aspire patches the existing DCP resource to start it rather than deleting and recreating it. This means the execution configuration callbacks run once during startup registration and are **not** re-evaluated when you manually start the resource. ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); From 9afa0c4164913836c4efd23dc1aafc670c31981c Mon Sep 17 00:00:00 2001 From: David Pine <7679720+IEvangelist@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:52:35 -0500 Subject: [PATCH 3/3] docs: add persistence diagnostic guidance Clarify the explicit-start examples and document ASPIREPERSISTENCE001 suppression options. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ca76c036-8248-4cdc-a1b2-0d4a1bef31f3 --- .../config/sidebar/reference.topics.ts | 4 + .../docs/app-host/resource-lifetimes.mdx | 9 ++- .../docs/diagnostics/aspirepersistence001.mdx | 80 +++++++++++++++++++ .../src/content/docs/diagnostics/overview.mdx | 1 + .../content/docs/whats-new/aspire-13-4.mdx | 3 +- 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx diff --git a/src/frontend/config/sidebar/reference.topics.ts b/src/frontend/config/sidebar/reference.topics.ts index 95d6c3dcc..6d80799da 100644 --- a/src/frontend/config/sidebar/reference.topics.ts +++ b/src/frontend/config/sidebar/reference.topics.ts @@ -726,6 +726,10 @@ export const referenceTopics: StarlightSidebarTopicsUserConfig[number] = { label: 'ASPIREHOSTINGPYTHON001', link: '/diagnostics/aspirehostingpython001', }, + { + label: 'ASPIREPERSISTENCE001', + link: '/diagnostics/aspirepersistence001', + }, { label: 'ASPIREPIPELINES001', link: '/diagnostics/aspirepipelines001', diff --git a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx index 3812b752f..874f2bd17 100644 --- a/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx +++ b/src/frontend/src/content/docs/app-host/resource-lifetimes.mdx @@ -13,7 +13,7 @@ Aspire resources support a number of different lifetime modes. For example, the Resource lifetimes apply to containers, executables, and projects. Persistent executable and project lifetimes are experimental in Aspire 13.4. You can use different lifetimes for resources that take time to initialize, need stable local endpoints, should remain available while you restart or rebuild the AppHost, or need to match another resource's lifetime. :::caution[Experimental shared lifetime APIs] -The shared lifetime APIs are experimental and emit diagnostic `ASPIREPERSISTENCE001`. The existing container-specific `WithLifetime(ContainerLifetime.Persistent)` and `WithLifetime(ContainerLifetime.Session)` APIs remain supported for container resources. +The shared lifetime APIs are experimental and emit the [`ASPIREPERSISTENCE001`](/diagnostics/aspirepersistence001/) diagnostic. The existing container-specific `WithLifetime(ContainerLifetime.Persistent)` and `WithLifetime(ContainerLifetime.Session)` APIs remain supported for container resources. ::: ## Lifetime modes @@ -94,6 +94,10 @@ var job = builder.AddExecutable("batch-job", "dotnet", ".", "run", "--project", builder.Build().Run(); ``` +:::note[Placeholder API used for demonstration] +`GetApiKeyAsync` is a placeholder used to demonstrate deferred, callback-based configuration. Its implementation isn't shown because the relevant behavior is retrieving a value when the callback runs and assigning that value to an environment variable on the target resource. +::: + ### Persistent explicit-start resources For persistent resources, Aspire must register the resource with the Developer Control Plane (DCP) immediately at startup so it can discover any existing running instance. However, when you manually start a persistent explicit-start resource, Aspire patches the existing DCP resource to start it rather than deleting and recreating it. This means the execution configuration callbacks run once during startup registration and are **not** re-evaluated when you manually start the resource. @@ -103,6 +107,7 @@ var builder = DistributedApplication.CreateBuilder(args); // Persistent explicit-start resources are registered at startup to detect existing instances. // The callback runs during startup registration — not again when manually started. +#pragma warning disable ASPIREPERSISTENCE001 var cache = builder.AddContainer("long-lived-cache", "my-cache-image") .WithPersistentLifetime() .WithExplicitStart() @@ -111,6 +116,8 @@ var cache = builder.AddContainer("long-lived-cache", "my-cache-image") context.EnvironmentVariables["CACHE_SIZE"] = "512mb"; }); +#pragma warning restore ASPIREPERSISTENCE001 + builder.Build().Run(); ``` diff --git a/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx b/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx new file mode 100644 index 000000000..5b5044e51 --- /dev/null +++ b/src/frontend/src/content/docs/diagnostics/aspirepersistence001.mdx @@ -0,0 +1,80 @@ +--- +title: Compiler Error ASPIREPERSISTENCE001 +seoTitle: 'ASPIREPERSISTENCE001: Experimental resource lifetime APIs' +description: Learn what causes the Aspire compiler error ASPIREPERSISTENCE001 and how to suppress it when using experimental resource lifetime APIs. +--- + +import { Badge } from '@astrojs/starlight/components'; + + + +> Resource lifetime types and members are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. + +This diagnostic error is reported when a C# AppHost uses the experimental shared resource lifetime APIs. These APIs configure session, persistent, parent-process, and resource-scoped lifetimes across supported resources. + +The following APIs and types are protected by this diagnostic: + +- `WithSessionLifetime` +- `WithPersistentLifetime` +- `WithParentProcessLifetime` +- `WithLifetimeOf` +- `PersistenceAnnotation` +- `PersistenceMode` + +## Example + +The following code generates `ASPIREPERSISTENCE001`: + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var worker = builder.AddExecutable("worker", "node", "../worker", "server.js") + .WithPersistentLifetime(); + +builder.Build().Run(); +``` + +## To correct this error + +Suppress the diagnostic when you intentionally opt in to the experimental shared lifetime APIs. For container resources, you can instead use the supported container-specific `WithLifetime(ContainerLifetime.Persistent)` and `WithLifetime(ContainerLifetime.Session)` APIs. + +Suppress the error with one of the following methods: + +- Set the severity of the rule in the _.editorconfig_ file. + + ```ini title=".editorconfig" + [*.{cs,vb}] + dotnet_diagnostic.ASPIREPERSISTENCE001.severity = none + ``` + + For more information about editor config files, see [Configuration files for code analysis rules](/diagnostics/overview/#suppress-in-the-editorconfig-file). + +- Add the following `PropertyGroup` to your project file: + + ```xml title="C# project file" + + $(NoWarn);ASPIREPERSISTENCE001 + + ``` + +- Suppress the diagnostic in code with the `#pragma warning disable ASPIREPERSISTENCE001` directive: + + ```csharp title="AppHost.cs" + var builder = DistributedApplication.CreateBuilder(args); + + #pragma warning disable ASPIREPERSISTENCE001 + var worker = builder.AddExecutable("worker", "node", "../worker", "server.js") + .WithPersistentLifetime(); + #pragma warning restore ASPIREPERSISTENCE001 + + builder.Build().Run(); + ``` + +## See also + +- [Configure resource lifetimes](/app-host/resource-lifetimes/) diff --git a/src/frontend/src/content/docs/diagnostics/overview.mdx b/src/frontend/src/content/docs/diagnostics/overview.mdx index 6f62e9a7e..20652f1c9 100644 --- a/src/frontend/src/content/docs/diagnostics/overview.mdx +++ b/src/frontend/src/content/docs/diagnostics/overview.mdx @@ -48,6 +48,7 @@ The following table lists the possible MSBuild and analyzer warnings and errors | [ASPIREJAVASCRIPT001](/diagnostics/aspirejavascript001/) | (Experimental) Warning | JavaScript hosting and publishing APIs are for evaluation purposes only and are subject to change or removal in future updates. | | [ASPIREHOSTINGPYTHON001](/diagnostics/aspirehostingpython001/) | (Experimental) Error | `AddPythonApp` is for evaluation purposes only and is subject to change or removal in future updates. | | [ASPIREMCP001](/diagnostics/aspiremcp001/) | (Experimental) Warning | MCP server types and members are for evaluation purposes only and are subject to change or removal in future updates. | +| [ASPIREPERSISTENCE001](/diagnostics/aspirepersistence001/) | (Experimental) Error | Resource lifetime types and members are for evaluation purposes only and are subject to change or removal in future updates. | | [ASPIREPIPELINES001](/diagnostics/aspirepipelines001/) | (Experimental) Error | Pipeline infrastructure APIs are for evaluation purposes only and are subject to change or removal in future updates. | | [ASPIREPIPELINES002](/diagnostics/aspirepipelines002/) | (Experimental) Error | Deployment state manager APIs are for evaluation purposes only and are subject to change or removal in future updates. | | [ASPIREPIPELINES003](/diagnostics/aspirepipelines003/) | (Experimental) Error | Container image build APIs are for evaluation purposes only and are subject to change or removal in future updates. | diff --git a/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx b/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx index e8a469472..cf99feb87 100644 --- a/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx +++ b/src/frontend/src/content/docs/whats-new/aspire-13-4.mdx @@ -455,7 +455,8 @@ Persistent lifetimes now extend beyond containers to executables and projects. C