diff --git a/src/frontend/config/sidebar/reference.topics.ts b/src/frontend/config/sidebar/reference.topics.ts index 42380fa31..ae9da7195 100644 --- a/src/frontend/config/sidebar/reference.topics.ts +++ b/src/frontend/config/sidebar/reference.topics.ts @@ -748,6 +748,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 854913c50..d53df1a26 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, plus explicit start, control Aspire containers, executables, and projects. --- import { Tabs, TabItem } from '@astrojs/starlight/components'; @@ -14,7 +14,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 @@ -44,7 +44,8 @@ Persistent containers use proxied endpoints by default, just like session contai You can still configure endpoint proxy behavior explicitly on any persistent resource. Set `isProxied: false` on an individual endpoint, or call `WithEndpointProxySupport(false)` to make every endpoint on a resource proxyless. When a proxyless endpoint doesn't specify a public `port`, Aspire allocates one before the resource is created. For persistent resources, Aspire stores the allocated port in user secrets when user secrets are available and reuses it on later AppHost runs. - For more information, see [Allocate ports for dynamic proxyless endpoints](/fundamentals/networking-overview/#allocate-ports-for-dynamic-proxyless-endpoints). + For more information, see [Allocate ports for dynamic proxyless + endpoints](/fundamentals/networking-overview/#allocate-ports-for-dynamic-proxyless-endpoints). :::danger[Persistent container ≠ persistent data] @@ -75,6 +76,59 @@ 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. + +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 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); + +// 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(); +``` + +:::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. + +```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. +#pragma warning disable ASPIREPERSISTENCE001 +var cache = builder.AddContainer("long-lived-cache", "my-cache-image") + .WithPersistentLifetime() + .WithExplicitStart() + .WithEnvironment(context => + { + context.EnvironmentVariables["CACHE_SIZE"] = "512mb"; + }); + +#pragma warning restore ASPIREPERSISTENCE001 + +builder.Build().Run(); +``` + ## Configure a persistent container For new code, configure a persistent container with `WithPersistentLifetime()`: 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 28bebfe39..d519c629e 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 433b96a95..359e4ea97 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 @@ -474,7 +474,8 @@ Persistent lifetimes now extend beyond containers to executables and projects. C