feat(strawberry-shake): persist IOperationResult via [PersistentState] (.NET 10)#2
Open
cliedeman wants to merge 2 commits into
Open
feat(strawberry-shake): persist IOperationResult via [PersistentState] (.NET 10)#2cliedeman wants to merge 2 commits into
cliedeman wants to merge 2 commits into
Conversation
…] (.NET 10) Adds a PersistentComponentStateSerializer<IOperationResult<TData>> so a Blazor component or service property of type IOperationResult<TData> annotated with the .NET 10 [PersistentState] attribute survives the prerender to interactive boundary without re-executing the operation. This is a more declarative alternative to the generated UsePersistentQuery component. - OperationResultBuilder captures the raw transport "data" payload into ContextData (gated by the CapturePersistedData virtual) and rebuilds a result from a persisted payload via BuildFromPersistedData. IOperationResultBuilder exposes BuildFromPersistedData as a throwing default-interface-method. - OperationResultPersistentStateSerializer<TData> (StrawberryShake.Razor, guarded by #if NET10_0_OR_GREATER) writes the captured payload and restores it through the result builder. Register it per result type with services.AddPersistentOperationResult<TData>(). - Tests: Core.Tests cover capture + BuildFromPersistedData round-trip; a new StrawberryShake.Razor.Tests project (net10/net11) covers the serializer Persist/Restore round-trip and the empty-data fallback. Relates to ChilliCream#7209. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ia codegen The generated client now registers a PersistentComponentStateSerializer per operation result type, so consumers no longer call AddPersistentOperationResult<T>() per type - AddXxxClient() wires them up. The registration is emitted behind #if NET10_0_OR_GREATER (the serializer type is .NET 10+), and the same opt-in turns on raw-payload capture in the generated result builders. - New RazorPersistedState generator setting, plumbed from .graphqlrc.json (StrawberryShakeSettings) through CSharpGeneratorSettings to CSharpSyntaxGeneratorSettings and CSharpGenerator. - JsonResultBuilderGenerator emits "protected override bool CapturePersistedData => true;" when the opt-in is set and a store is enabled. - DependencyInjectionGenerator emits, per operation result type, a guarded AddSingleton<PersistentComponentStateSerializer<IOperationResult<T>>, OperationResultPersistentStateSerializer<T>>(). All registrations reuse the single generic serializer class as singletons. - Razor codegen snapshot covers the emitted registration and capture override. Relates to ChilliCream#7209. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A declarative way to persist StrawberryShake results across the Blazor prerender → interactive boundary (related to ChilliCream#7209), using the .NET 10
[PersistentState]attribute directly on anIOperationResult<T>property. No generatedUseQuery-style component (cf. the companion PR), and — as of the latest commit — no per-type registration:On prerender
Resultis persisted; on the interactive client it's restored without re-executing the operation; the??=skips the call. Opt in with"razorPersistedState": truein.graphqlrc.json(requires a store and a reference to StrawberryShake.Razor).How it works
.NET 10 allows a custom serializer to be plugged into
[PersistentState]viaPersistentComponentStateSerializer<T>(a DI singleton). This sidesteps the interface-serialization wall: the serializer persists the raw transportdatapayload and rebuilds via the operation's result builder.OperationResultBuildercaptures the rawdataelement intoContextData(gated by theCapturePersistedDatavirtual) and rebuilds a result from a persisted payload viaBuildFromPersistedData.IOperationResultBuilderexposesBuildFromPersistedDataas a throwing default-interface-method (non-JSON transports unaffected).OperationResultPersistentStateSerializer<TData>(StrawberryShake.Razor,#if NET10_0_OR_GREATER):Persistwrites the captured payload;Restorerebuilds it through the result builder.razorPersistedState): the result builders emitCapturePersistedData => true, and the generatedAddFooClient()registers, per result type and behind#if NET10_0_OR_GREATER,AddSingleton<PersistentComponentStateSerializer<IOperationResult<T>>, OperationResultPersistentStateSerializer<T>>(). All registrations reuse the single generic serializer class as singletons.AddPersistentOperationResult<TData>()remains available for manual/edge cases.Trade-offs vs the
UsePersistentQueryapproach#if-guarded; net8/9 consumers compile with the registration excluded).Resultis a one-time value (noWatch/store-update reactivity), which is the typical need for prerender hydration.Tests
StrawberryShake.Razor.Tests(net10/net11): serializerPersist/Restoreround-trip (no re-execution) and the empty-data fallback.Core.Tests: capture intoContextDataandBuildFromPersistedDataround-trip.#if-guarded serializer registration and theCapturePersistedDataoverride.🤖 Generated with Claude Code