Skip to content

[Blazor] Make component serialization metadata-first - #68295

Draft
javiercn wants to merge 14 commits into
mainfrom
javiercn-aot-stack-1-stj
Draft

[Blazor] Make component serialization metadata-first#68295
javiercn wants to merge 14 commits into
mainfrom
javiercn-aot-stack-1-stj

Conversation

@javiercn

@javiercn javiercn commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

Stack layer 1/6 for the Aspire Dashboard Native AOT work. This PR is intentionally limited to System.Text.Json and serialization contracts and targets main independently.

  • generates fixed framework contracts for import maps/resources, JavaScript initializers, TempData, circuit persistence, component marker envelopes, render-fragment wire DTOs, and framework JS payloads
  • preserves component marker protocol settings by resolving generated contracts through the protocol-owned options (camelCase names and null omission)
  • completes framework-owned contracts for the initializer response, JS component parameters, and prerender application state
  • normalizes Server renderer IDs to their integer wire shape and corrects the Endpoints linker substitution target
  • exposes the minimal experimental RazorComponentsMetadataContext.JsonTypeInfoResolver surface and AddComponentMetadata<TContext>()
  • composes application contracts ahead of the reflection compatibility resolver using immutable, service-provider-owned snapshots
  • defers WebAssembly prerender parameter deserialization until provider creation, while preserving registered SSR root precedence ahead of manually configured roots
  • routes later WebAssembly updates through host-owned serialization state
  • adds generic protected browser storage writes and ProtectedBrowserStorageSerializer<T>
  • establishes nullable annotation context in shared marker settings so linked nullable-disabled projects compile the resolver signatures correctly

Review focus

Please review only the STJ/serialization design in this layer: contract ownership, resolver ordering, provider/host isolation, marker protocol compatibility, WebAssembly registered-root startup ordering, and protected storage. JS-invokable descriptors, binding/component metadata, framework providers, and Native AOT E2E proof are intentionally deferred to later stack layers.

Validation

  • Components Endpoints unit suite: 813 passed
  • focused marker/cache protocol tests: 75 passed
  • WebAssembly unit suite: 55 passed
  • registered/manual root-order unit regression: passed
  • dependency-aware Components E2ETest Debug build: succeeded with 0 warnings/errors
  • targeted client-root E2E witness (ClientRenderingMultpleComponentsTest.CanRenderMultipleRootComponents): passed in Debug JIT
  • dependency-aware Components E2ETest Release build via eng/build.cmd: succeeded with 0 warnings/errors
  • representative Server interactive E2E witness (ServerComponentRenderingTest.CanRenderTextOnlyComponent): passed
  • Components Web unit suite: 316 passed
  • Components Server unit suite: 405 passed
  • prerender application store tests: 2 passed
  • LinkabilityChecker repository build: succeeded with 0 warnings/errors
  • focused Components persistent-state and JSInterop serialization tests from the original layer validation

Source draft PR #68291 remains open and untouched.

javiercn and others added 8 commits August 8, 2026 20:29
Resolve framework-owned import map, resource, initializer, TempData, circuit, and persistence payloads through generated contracts.

Behavior: changed: fixed framework payloads no longer require reflection metadata
State: incomplete - ServerComponentJsonContext is completed by #2
Review hint: application-owned values are deliberately unchanged

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Generate the server and WebAssembly marker envelopes and route fixed render-fragment wire DTOs through JsonTypeInfo overloads. Application parameter values continue to use the reflection compatibility resolver.

Behavior: changed: framework marker envelopes resolve without reflection
State: complete
Review hint: contracts stop at the application parameter-value boundary

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Make JSRuntime's reflection fallback explicit, serialize outbound object arrays through JsonTypeInfo, and compose Web renderer and converter-backed contracts before that fallback. Custom events now use the JsonTypeInfo overload.

Behavior: changed: framework JS payloads resolve generated or converter-backed contracts first
State: builds; focused coverage follows in #4
Review hint: no JS-invokable dispatch metadata is included

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise reflection-disabled fixed payloads, generated-before-reflection ordering, converter-backed Web renderer arguments, and the corrected custom-event JsonTypeInfo path.

Behavior: preserved
State: complete for fixed framework serialization
Review hint: remote tests prove behavior with STJ reflection disabled

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add the minimal RazorComponentsMetadataContext JSON surface and compose registered resolvers into provider-owned persistence, marker, Session, custom-event, Web renderer, and WebAssembly host options. Prerendered and updated WebAssembly markers now deserialize after provider creation through immutable host state.

Behavior: changed: application contracts resolve before reflection without crossing service-provider boundaries
State: builds; host-isolation coverage follows in #6
Review hint: resolver snapshots are instance-owned; no application resolver mutates a process static

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Verify resolver composition is additive, existing providers and serializers remain immutable, reflection-disabled marker values use only registered contracts, and WebAssembly host options snapshot each provider.

Behavior: preserved
State: complete for application JSON composition
Review hint: each test builds distinct providers from one service collection to expose resolver leakage

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add generic SetAsync overloads and ProtectedBrowserStorageSerializer<T>, while JSON fallback uses the provider-scoped application contract snapshot and preserves shipped constructors.

Behavior: changed: applications can provide typed protected-storage serializers or generated JSON contracts
State: builds; API and runtime coverage follows in #8
Review hint: direct construction retains the shared compatibility options; DI instances are provider-isolated

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cover provider-scoped JSON options, multiple contexts, custom typed serializers, runtime-type fallback, direct-construction compatibility, and failure behavior for protected browser storage.

Behavior: preserved
State: complete
Review hint: the tests exercise both DI-created and shipped-constructor instances

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
javiercn and others added 4 commits August 9, 2026 13:11
Generate the remaining initializer and prerender-state contracts, use explicit JsonTypeInfo overloads for JS component parameters, normalize Server renderer IDs to their integer wire shape, and correct the Endpoints linker substitution target.

Behavior: changed: remaining framework-owned payloads no longer depend on reflection or enum wire conversion
State: builds; linker descriptor coverage follows in #10
Review hint: these are the final serialization-owned hunks from the frozen donor audit

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Verify the embedded substitution descriptor targets the assembly that owns the HotReloadManager use so future moves cannot silently reintroduce the Forms assembly name.

Behavior: preserved
State: complete

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Create the provider-owned WebAssembly marker serializer on demand when a manually assembled renderer service provider does not register it, while normal hosts continue to reuse the DI singleton.

Behavior: preserved: standalone renderer construction continues to emit WebAssembly markers
State: complete
Review hint: the fallback still snapshots resolver contexts from the current request provider

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Shared marker settings are linked into nullable-disabled projects, so establish their annotation context locally before declaring nullable application resolvers.

Behavior: preserved
State: complete
Review hint: both files are shared source; the E2ETest compile gate is the regression witness

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
javiercn and others added 2 commits August 9, 2026 17:35
Resolve generated marker and cached render-fragment contracts through ServerComponentSerializationSettings so camelCase naming and null omission remain part of the wire protocol.

Behavior: changed: emitted component comments again use lowercase protocol names and omit null-only fields
State: complete
Review hint: raw start/end marker assertions cover the browser parser contract; direct generated contexts remain only where no custom protocol options apply

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Materialize provider-deserialized SSR root mappings and insert them before manually configured roots, preserving historical startup precedence without restoring pre-provider deserialization.

Behavior: changed: registered marker-backed roots start before manual mappings again
State: complete
Review hint: two registered and two manual mappings prove order is stable within and across both groups

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant