[docs] Document collection property behavior in polyglot SDK DTOs#1017
[docs] Document collection property behavior in polyglot SDK DTOs#1017aspire-repo-bot[bot] wants to merge 1 commit into
Conversation
DTO collection properties (List<T>, arrays, Dictionary<string,T>) are now generated as value-shaped JSON types in TypeScript, Python, Go, and Java. Previously they incorrectly used handle-backed wrappers like AspireList<T>, preventing callers from using plain collection literals. Adds a 'Collection properties in DTOs' subsection with language-tabbed examples and a note clarifying the distinction from exported resource properties. Fixes documentation gap surfaced by microsoft/aspire#17098. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds documentation clarifying how collection-typed properties on [AspireDto] classes are represented in generated guest SDKs (TypeScript, Python, Go, Java), addressing a previously incorrect handle-backed representation and showing how to supply plain collection literals.
Changes:
- Added a new “Collection properties in DTOs” subsection under “Export configuration DTOs”.
- Included a concrete DTO example and usage snippets across TypeScript, Python, Go, and Java via
Tabs. - Documented the distinction between DTO collection properties (value-shaped JSON) vs
[AspireExport]mutable collections (handle-backed wrappers).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <TabItem label="Go"> | ||
|
|
||
| ```go title="Go — apphost.go" | ||
| AddMyResource(AspireNspAccessRule{ |
IEvangelist
left a comment
There was a problem hiding this comment.
Generated by running the
doc-testerskill against the rendered page on a local docs site (aspire run+ Playwright). Review-only — no files were modified, committed, or pushed.
Summary
This PR adds a useful new "Collection properties in DTOs" subsection under Export configuration DTOs in multi-language-integration-authoring.mdx. The page renders cleanly on the local Astro/Starlight site, the four-language Tabs/TabItem block works, the new heading is in the right place in the table of contents, the anchor #collection-properties-in-dtos resolves, and the closing :::note correctly contrasts DTOs with [AspireExport] resource types (which still use AspireList<T>/AspireDict<K,V>).
There is one blocking issue in the Go example (wrong DTO type name) and a couple of clarity findings about example consistency. Once the Go typo is fixed, this is a good addition.
Findings
🔴 1. Go example uses a non-existent DTO type — AspireNspAccessRule (blocker)
Location: src/frontend/src/content/docs/extensibility/multi-language-integration-authoring.mdx, Go tab inside Collection properties in DTOs.
What the doc says:
// Go — apphost.go
AddMyResource(AspireNspAccessRule{
AddressPrefixes: []string{"203.0.113.0/24", "198.51.100.0/24"},
FullyQualifiedDomainNames: []string{"example.com"},
})Problem: The C# DTO declared immediately above the tabs is AzureNspAccessRule, and that is the name used in the TypeScript, Python and Java tabs as well. AspireNspAccessRule is not a real type — it does not appear anywhere else in the docs repo, the twoslash SDK snapshot (src/frontend/src/data/twoslash/aspire.d.ts declares export interface AzureNspAccessRule), or in src/frontend/src/content/docs/whats-new/aspire-13-3.mdx where the type is also referenced as AzureNspAccessRule. A reader copying this Go snippet will reference a type that doesn't exist.
Suggested fix:
AddMyResource(AzureNspAccessRule{
AddressPrefixes: []string{"203.0.113.0/24", "198.51.100.0/24"},
FullyQualifiedDomainNames: []string{"example.com"},
})🟡 2. Example shape is inconsistent between language tabs (minor)
The tabs supposedly demonstrate the same call in different languages, but they are structurally different:
- TypeScript:
addMyResource({ accessRules: [ { addressPrefixes: [...], fullyQualifiedDomainNames: [...] } ] }); - Python:
add_my_resource(access_rules=[AzureNspAccessRule(address_prefixes=[...], fully_qualified_domain_names=[...])]) - Go:
AddMyResource(AspireNspAccessRule{ AddressPrefixes: []string{...}, FullyQualifiedDomainNames: []string{...} }) - Java:
addMyResource(new AzureNspAccessRule().setAddressPrefixes(List.of(...)).setFullyQualifiedDomainNames(List.of(...)));
TypeScript and Python pass a list of access rules wrapped in an outer options object (accessRules: [...] / access_rules=[...]). Go and Java pass a single rule directly as the argument. The section's stated point — "value-shaped collection literals" — is shown in every language (the inner AddressPrefixes/addressPrefixes/address_prefixes lists are plain literals), so the demonstration still works, but the outer call shape mismatch is distracting and may make readers wonder whether the four signatures correspond to the same or different APIs.
Suggested fix (pick one):
- Use the same shape across all four tabs (either all pass a single
AzureNspAccessRule, or all pass an options DTO containingaccessRules: [...]). - Or, more simply, drop the outer
addMyResource(...)call entirely and just show the four DTO/struct/object literals on their own, since the point is the shape of the DTO literal, not the host method.
🟡 3. The hypothetical addMyResource(...) doesn't match the real API for AzureNspAccessRule (minor)
The PR borrows a real, recognizable Azure type (AzureNspAccessRule) but invents a top-level addMyResource({ accessRules: [...] }) call. The generated TS SDK that ships with the docs (aspire.d.ts) exposes this type via withAccessRule(rule: AzureNspAccessRule): this on the NSP builder — i.e., a single rule, no accessRules array, no addMyResource symbol.
Readers who recognize the type and try to apply the documented TS syntax against the actual withAccessRule will get something different from what's shown. Either:
- Rename the example DTO to something obviously made-up (e.g.,
MyAccessRuleon a fictional integration) so readers don't conflate it with the Azure NSP API, or - Match the real API and write the TS tab as
nsp.withAccessRule({ addressPrefixes: [...], fullyQualifiedDomainNames: [...] });(and align the other languages accordingly).
🟢 4. Optional enhancement: forward-link from "Export resource types" (nit)
The existing Export resource types section (around line 200) describes how mutable collection properties on [AspireExport] types become AspireList<T> getters. The new DTO section sets up an explicit contrast with this case. A short forward link from the resource-types section to Collection properties in DTOs would help readers discover that DTOs follow a different rule.
Knowledge Gaps
Knowledge Gap: Actual generated TS SDK shape for DTO List<T> properties
What I needed to know: Whether the generator currently emits DTO List<T> properties as value-shaped TS arrays (e.g., addressPrefixes: string[]) as the new section claims.
Source of my knowledge: I only checked the shipped twoslash snapshot in this branch (src/frontend/src/data/twoslash/aspire.d.ts). It still types AzureNspAccessRule.addressPrefixes as List<string>, where List<T> is declared as an opaque empty interface (export interface List<T = unknown> {}). That is not a value-shaped array literal type.
User impact: If a user follows the new doc and writes addressPrefixes: ["203.0.113.0/24"] against the current SDK declarations, TypeScript will reject it. The PR description states the documented behavior comes from microsoft/aspire#17098, which the author indicates targets the 13.4 milestone — so the twoslash data probably just needs to be regenerated against that change.
Recommendation:
- Confirm microsoft/aspire#17098 is merged into the 13.4 product branch before this docs PR ships in 13.4.
- Regenerate
src/frontend/src/data/twoslash/aspire.d.ts(or whichever generator owns the SDK type snapshots) so the on-site TS twoslash examples and any future hover-types reflect the new value-shaped collection types and stay consistent with the new prose.
Knowledge Gap: I did not run an end-to-end multi-language sample
What I needed to know: Whether aspire run against an integration that declares [AspireDto] class Foo { public List<string>? Bar { get; set; } } actually generates value-shaped types in TS/Python/Go/Java SDKs today.
Source of my knowledge: None — I did not build a sample integration, because doing so wouldn't be meaningful until microsoft/aspire#17098 is in the CLI/SDK my environment installs.
Recommendation: Reviewer / merger should run one end-to-end smoke test with a [AspireDto] containing a List<T> property after the underlying product PR lands, to confirm all four languages match this doc.
Verdict: REQUEST_CHANGES
Blocking on finding 1 (Go example references a type that does not exist). Findings 2 and 3 are clarity improvements worth folding into the same revision if convenient.
Documents changes from microsoft/aspire#17098 by
@sebastienros.Targeting
release/13.4based on the source PR milestone13.4.Why this PR is needed
Prior to microsoft/aspire#17098, DTO collection properties (such as
List<string>) in[AspireDto]-annotated classes were erroneously generated as handle-backed wrapper types (AspireList<T>) in TypeScript, Python, Go, and Java. This prevented users from using plain collection literals (e.g.,addressPrefixes: ["203.0.113.0/24"]). The fix makes DTO collection properties generate as value-shaped JSON types, which is the correct and user-expected behavior.The existing "Export configuration DTOs" section only showed simple scalar properties and did not explain how collection properties behave or how to use them in guest SDKs.
What changed
Added a "Collection properties in DTOs" subsection to
multi-language-integration-authoring.mdxthat:List<T>,IList<T>,IEnumerable<T>, arrays, andDictionary<string, T>properties on[AspireDto]classes generate as value-shaped types.AzureNspAccessRulewithAddressPrefixes) in all four supported languages (TypeScript, Python, Go, Java) using theTabscomponent.[AspireExport]), where mutable collections continue to useAspireList<T>.Files modified
src/frontend/src/content/docs/extensibility/multi-language-integration-authoring.mdx— updated