Skip to content

Consolidate WithEnvironment polyglot exports using AspireUnionAttribute#15333

Merged
joperezr merged 10 commits intorelease/13.2from
consolidate-withenvironment-union
Mar 18, 2026
Merged

Consolidate WithEnvironment polyglot exports using AspireUnionAttribute#15333
joperezr merged 10 commits intorelease/13.2from
consolidate-withenvironment-union

Conversation

@maddymontaquila
Copy link
Contributor

Summary

Consolidates the separate withEnvironment and withEnvironmentExpression polyglot exports into a single withEnvironment export using [AspireUnion], giving TypeScript AppHost users a cleaner API.

Before (TypeScript)

// Plain string — one method
await api.withEnvironment("KEY", "value");

// Expression — different method name!
const endpoint = await redis.getEndpoint("tcp");
await api.withEnvironmentExpression("REDIS_URL", refExpr`redis://${endpoint}:6379`);

// EndpointReference — not available at all

After (TypeScript)

// All through a single method with union type
await api.withEnvironment("KEY", "value");
await api.withEnvironment("REDIS_URL", refExpr`redis://${endpoint}:6379`);
await api.withEnvironment("SERVICE_URL", endpoint);

Changes

src/Aspire.Hosting/ResourceBuilderExtensions.cs

  • Removed [AspireExport] from the individual WithEnvironment(name, string) and WithEnvironment(name, ReferenceExpression) overloads
  • Added new consolidated export method using [AspireUnion(typeof(string), typeof(ReferenceExpression), typeof(EndpointReference))] on the value parameter
  • Also surfaces EndpointReference which was previously not exported to polyglot hosts

src/Aspire.Hosting.RemoteHost/Ats/AtsMarshaller.cs

  • Added object target type handling in ConvertPrimitive — when the target type is object (union parameter), infers the correct .NET type from the JSON value structure (string → string, bool → bool, etc.)
  • Handles/expressions already resolved correctly before this point in the unmarshalling pipeline

Snapshot updates

  • HostingContainerResourceCapabilities.verified.txtwithEnvironmentExpression capability removed (folded into withEnvironment)
  • TwoPassScanningGeneratedAspire.verified.ts — generated TypeScript now shows value: string | ReferenceExpression | EndpointReference union type

Testing

  • Aspire.Hosting builds clean (0 warnings)
  • Aspire.Hosting.RemoteHost builds clean
  • ✅ All 56 TypeScript codegen tests pass
  • ✅ All 210 RemoteHost tests pass (marshaller changes)
  • ✅ Hosting tests: 2098/2113 pass (15 failures are pre-existing .NET runtime version issues)

@github-actions
Copy link
Contributor

github-actions bot commented Mar 17, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15333

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15333"

@sebastienros sebastienros changed the base branch from main to release/13.2 March 17, 2026 23:06
@sebastienros sebastienros force-pushed the consolidate-withenvironment-union branch from 8f77857 to 2ea73e3 Compare March 17, 2026 23:08
maddymontaquila and others added 5 commits March 17, 2026 16:37
Merge the separate withEnvironment (string) and withEnvironmentExpression
(ReferenceExpression) polyglot exports into a single withEnvironment export
using [AspireUnion] to accept string | ReferenceExpression | EndpointReference.

This gives TypeScript AppHost users a single, clean API:
  await api.withEnvironment('KEY', 'value');
  await api.withEnvironment('KEY', refExpr`redis://${endpoint}`);
  await api.withEnvironment('KEY', endpoint);

Instead of requiring separate method names for each value type.

Also adds EndpointReference support which was previously unexported,
and handles object target type in AtsMarshaller.ConvertPrimitive for
correct union parameter deserialization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, and IValueProvider fallback

- Add IResourceBuilder<ParameterResource> and IResourceBuilder<IResourceWithConnectionString> to the AspireUnion
- Add IValueProvider+IManifestExpressionProvider runtime fallback for extensibility (e.g. BicepOutputReference)
- Remove withEnvironmentEndpoint separate export (covered by union)
- Remove sync withEnvironmentCallback export, rename async to withEnvironmentCallback
- Update codegen snapshots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros force-pushed the consolidate-withenvironment-union branch from 8916f49 to 389967d Compare March 17, 2026 23:39
@github-actions
Copy link
Contributor

The transient CI rerun workflow requested reruns for the following jobs after analyzing the failed attempt.
GitHub's job rerun API also reruns dependent jobs, so the retry is being tracked in the rerun attempt.
The job links below point to the failed attempt that matched the retry-safe transient failure rules.

  • Tests / Hosting.MongoDB / Hosting.MongoDB (windows-latest) - Failed step 'Upload logs, and test results | Generate test results summary' will be retried because the job log shows a likely transient infrastructure network failure. Matched pattern: /api.github.com.{0,160}(timed out|failed to connect|failed to respond|could not resolve|ENOTFOUND|ECONNRESET|EPROTO|Bad Gateway|SSL connection could not be established)/i.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros marked this pull request as ready for review March 18, 2026 00:35
Copilot AI review requested due to automatic review settings March 18, 2026 00:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Aspire’s polyglot (ATS) surface for setting environment variables by consolidating multiple exported capabilities into a single withEnvironment overload that accepts a union-typed value, and updates the ATS marshalling logic and generated SDK snapshots accordingly.

Changes:

  • Replaces per-type withEnvironment* exports (e.g., withEnvironmentExpression, withEnvironmentCallbackAsync) with a unified exported withEnvironment union overload plus a single exported async callback capability.
  • Updates ATS marshalling to better handle object (union) primitive conversion.
  • Refreshes multi-language codegen snapshots and updates the TypeScript validation playground to use the new API.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Aspire.Hosting/ResourceBuilderExtensions.cs Introduces exported union withEnvironment overload; adjusts callback export; removes some prior exports.
src/Aspire.Hosting.RemoteHost/Ats/AtsMarshaller.cs Adds object-target primitive inference for union parameters.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts Snapshot updates reflecting consolidated withEnvironment and callback export changes.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/HostingContainerResourceCapabilities.verified.txt Removes capabilities that are no longer exported (withEnvironmentCallbackAsync, withEnvironmentExpression).
tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs Snapshot updates aligning Rust bindings with consolidated with_environment.
tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py Snapshot updates aligning Python bindings with consolidated with_environment.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java Snapshot updates aligning Java bindings with consolidated withEnvironment.
tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go Snapshot updates aligning Go bindings with consolidated WithEnvironment.
playground/polyglot/TypeScript/Aspire.Hosting/ValidationAppHost/apphost.ts Updates sample usage to call the unified withEnvironment overload.
playground/polyglot/TypeScript/Aspire.Hosting.SqlServer/aspire.config.json Adds config for the TypeScript SqlServer polyglot playground.

sebastienros and others added 2 commits March 17, 2026 17:57
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros added the Servicing-consider Issue for next servicing release review label Mar 18, 2026
@sebastienros
Copy link
Contributor

Verified locally

image
const container1 = await builder.addContainer("mycontainer1", "nginx");

const container2 = await builder.addContainer("mycontainer2", "nginx");

const configParam = await builder.addParameterFromConfiguration("myconfig", "MyConfig:Key");

await container2.withHttpEndpoint({ name: "http", targetPort: 80 });
const endpoint = await container2.getEndpoint("http");
const expr = refExpr`Host=${endpoint}`;
const envConnectionString = await builder.addConnectionString("envcs");

// withEnvironment — with EndpointReference
await container1.withEnvironment("MY_ENDPOINT", endpoint);

// withEnvironment — with ReferenceExpression
await container1.withEnvironment("MY_EXPR", expr);

// withEnvironment — with ParameterResource
await container1.withEnvironment("MY_PARAM", configParam);

// withEnvironment — with connection string resource
await container1.withEnvironment("MY_CONN", envConnectionString);

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 52 recordings uploaded (commit fdc3343)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23256120144

@joperezr joperezr removed the Servicing-consider Issue for next servicing release review label Mar 18, 2026
{
return d;
}
return value.ToJsonString();
Copy link
Member

Choose a reason for hiding this comment

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

hmm not sure I follow here. Should this be throw new ArgumentException() or something like that?

@joperezr joperezr added the Servicing-approved Approved for servicing release label Mar 18, 2026
@joperezr joperezr merged commit c25c645 into release/13.2 Mar 18, 2026
504 of 507 checks passed
@joperezr joperezr deleted the consolidate-withenvironment-union branch March 18, 2026 19:59
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Mar 18, 2026
Copilot AI pushed a commit that referenced this pull request Mar 18, 2026
…te (#15333)

* Consolidate WithEnvironment exports using AspireUnionAttribute

Merge the separate withEnvironment (string) and withEnvironmentExpression
(ReferenceExpression) polyglot exports into a single withEnvironment export
using [AspireUnion] to accept string | ReferenceExpression | EndpointReference.

This gives TypeScript AppHost users a single, clean API:
  await api.withEnvironment('KEY', 'value');
  await api.withEnvironment('KEY', refExpr`redis://${endpoint}`);
  await api.withEnvironment('KEY', endpoint);

Instead of requiring separate method names for each value type.

Also adds EndpointReference support which was previously unexported,
and handles object target type in AtsMarshaller.ConvertPrimitive for
correct union parameter deserialization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Expand WithEnvironment union with ParameterResource, ConnectionString, and IValueProvider fallback

- Add IResourceBuilder<ParameterResource> and IResourceBuilder<IResourceWithConnectionString> to the AspireUnion
- Add IValueProvider+IManifestExpressionProvider runtime fallback for extensibility (e.g. BicepOutputReference)
- Remove withEnvironmentEndpoint separate export (covered by union)
- Remove sync withEnvironmentCallback export, rename async to withEnvironmentCallback
- Update codegen snapshots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update TypeScript apphost environment usage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refresh polyglot codegen snapshots

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add ReferenceExpression apphost coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix TypeScript polyglot validation apphost

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Make WithEnvironment union overload internal

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address WithEnvironment review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: David Fowler <davidfowl@gmail.com>
Co-authored-by: Sebastien Ros <sebastienros@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants