Skip to content

v9 (2/3): Native AOT support - #2183

Draft
slang25 wants to merge 8 commits into
slang25/v9-foundationfrom
slang25/v9-aot
Draft

v9 (2/3): Native AOT support#2183
slang25 wants to merge 8 commits into
slang25/v9-foundationfrom
slang25/v9-aot

Conversation

@slang25

@slang25 slang25 commented Jun 22, 2026

Copy link
Copy Markdown
Member

v9 stacked PR — 2 of 3 — Native AOT

Makes JustSaying Native AOT-compatible. Stacked on #2182 (review/merge that first).

Highlights

  • System.Text.Json source-generated serialization path, gated so reflection is used only when available.
  • Default serializer flipped Newtonsoft → System.Text.Json (Newtonsoft remains fully supported as an opt-in). This is the key lever: it makes the core happy path AOT-clean.
  • IsAotCompatible enabled repo-wide. The only fix required was [DynamicallyAccessedMembers] on the DI handler type parameter — there are no [RequiresUnreferencedCode] annotations on AddJustSaying; reflection is confined to the Newtonsoft escape hatch.
  • tests/JustSaying.AotTest: a PublishAot app running a full publish → subscribe → handle round trip in-memory — verified locally producing and running a clean native binary — plus a native-aot (linux) CI job that publishes and runs it.
  • Also drops the Message constraint from the DI handler-registration extensions (a foundation follow-up that landed in this range).

294 unit tests + the native-AOT round trip green; solution builds clean with analyzers on.

Stack

  1. v9 (1/3): Drop the Message base-class constraint #2182 — Foundation
  2. this PR — Native AOT (base: slang25/v9-foundation)
  3. slang25/v9-cloudevents — CloudEvents + multi-type-per-queue

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds Native AOT support while making System.Text.Json the default serializer.

Changes:

  • Adds source-generated serialization and AOT annotations.
  • Adds an AOT round-trip test and CI job.
  • Updates DI defaults, samples, and migration guidance.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.github/workflows/build.yml Runs native AOT tests in CI.
Directory.Build.props Enables AOT compatibility analysis.
JustSaying.slnx Adds the AOT test project.
MIGRATION-v9.md Documents the serializer change.
samples/.../ApplicationJsonContext.cs Defines sample serialization metadata.
samples/.../Program.cs Configures source-generated serialization.
src/...Microsoft/IServiceCollectionExtensions.cs Updates DI defaults and AOT annotations.
src/...StructureMap/JustSayingRegistry.cs Changes StructureMap’s default serializer.
src/...StructureMap/RegistryExtensions.cs Relaxes handler message constraints.
src/JustSaying/AwsTools/MessageHandling/SnsPolicyBuilder.cs Uses generated policy metadata.
src/JustSaying/AwsTools/QueueCreation/RedrivePolicy.cs Uses generated redrive metadata.
src/JustSaying/Extensions/JsonSerializerOptionsExtensions.cs Resolves typed serialization metadata.
src/JustSaying/Fluent/ServiceResolver/DefaultServiceResolver.cs Changes the fluent default serializer.
src/JustSaying/JustSayingSerializationContext.cs Defines internal serialization metadata.
src/.../NewtonsoftMessageBodySerializer\1.cs` Adds trimming and AOT annotations.
src/.../NewtonsoftSerializationFactory.cs Marks Newtonsoft’s reflection requirements.
src/.../SystemTextJsonMessageBodySerializer.cs Makes default options AOT-aware.
src/.../SystemTextJsonMessageBodySerializer\1.cs` Adds source-generated serialization paths.
tests/JustSaying.AotTest/AotRoundTripTests.cs Tests a native publish/handle round trip.
tests/JustSaying.AotTest/JustSaying.AotTest.csproj Configures the Native AOT test app.
tests/.../WhenUsingDefaultServiceResolver.cs Verifies the new serializer default.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread MIGRATION-v9.md Outdated
Comment thread MIGRATION-v9.md Outdated
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.59%. Comparing base (b4d3c06) to head (ddf6baa).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...alization/SystemTextJsonMessageBodySerializer`1.cs 33.33% 2 Missing and 2 partials ⚠️
...ying/Extensions/JsonSerializerOptionsExtensions.cs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@                    Coverage Diff                    @@
##           slang25/v9-foundation    #2183      +/-   ##
=========================================================
- Coverage                  78.69%   78.59%   -0.11%     
=========================================================
  Files                        154      155       +1     
  Lines                       4013     4022       +9     
  Branches                     675      679       +4     
=========================================================
+ Hits                        3158     3161       +3     
- Misses                       545      549       +4     
- Partials                     310      312       +2     
Flag Coverage Δ
linux 78.54% <77.77%> (-0.11%) ⬇️
macos 61.40% <76.92%> (+0.01%) ⬆️
windows 61.40% <76.92%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@slang25
slang25 force-pushed the slang25/v9-aot branch 2 times, most recently from 2a004d4 to c7dfaa3 Compare August 9, 2026 11:44
slang25 and others added 8 commits August 11, 2026 01:30
Add a source-generated serialization path to SystemTextJsonMessageBodySerializer<T>
alongside the reflection-based one. On net8.0+, serialization branches on
JsonSerializer.IsReflectionEnabledByDefault: the reflection path is used when
available, otherwise the source-generated JsonTypeInfo<T> from the supplied
options is used (throwing the documented NotSupportedException if the type is
absent from the context). Both paths serialize by the declared type T, so output
is identical across reflection, trimming and AOT.

- New JsonSerializerOptionsExtensions.GetTypeInfo<T>() (net8.0+).
- DefaultJsonSerializerOptions only adds the (dynamic-code-requiring)
  JsonStringEnumConverter when dynamic code is supported.
- The parameterless ctor is annotated [RequiresUnreferencedCode]/
  [RequiresDynamicCode] because the default options carry no TypeInfoResolver.

Behaviour under the JIT is unchanged; 294/294 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change the default IMessageBodySerializationFactory from Newtonsoft.Json to
System.Text.Json across all three registration paths (the fluent default
service resolver, the Microsoft.Extensions.DependencyInjection extensions and
the StructureMap registry). This makes the default path source-generator- and
Native-AOT-friendly.

Newtonsoft.Json remains fully supported as an explicit opt-in. The change is a
behavioural one (the default wire format) and is captured in MIGRATION-v9.md.

Unit suite 294/294 (the one test that asserted the Newtonsoft default now
asserts System.Text.Json).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the library's own JSON handling source-generator-friendly and annotate the
Newtonsoft escape hatch ahead of enabling IsAotCompatible:

- New internal source-generated JustSayingSerializationContext for the two
  internal JSON shapes (RedrivePolicy and the SNS account-id list); RedrivePolicy
  and SnsPolicyBuilder use it on net8.0+, falling back to reflection elsewhere.
- NewtonsoftMessageBodySerializer<T> and NewtonsoftSerializationFactory are
  annotated [RequiresUnreferencedCode]/[RequiresDynamicCode] on their
  constructors, with [UnconditionalSuppressMessage] on the serialize/deserialize
  members (the caller opts into Newtonsoft's trimming/AOT requirements).

Behaviour is unchanged under the JIT; 294/294 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn on IsAotCompatible for all net8.0+ targets, which enables the trim/AOT/
single-file analyzers repo-wide. Thanks to the System.Text.Json default and the
source-generated internal JSON, the only required fix was annotating the handler
type parameter on AddJustSayingHandler<TMessage, THandler> with
[DynamicallyAccessedMembers(PublicConstructors)] so DI can construct it under
trimming. The core happy path is AOT-clean with no [RequiresUnreferencedCode] on
AddJustSaying - reflection requirements are confined to the Newtonsoft escape
hatch.

Whole solution builds clean with analyzers on (0 IL/CS/RS); 294/294 unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prove the full publish -> subscribe -> handle pipeline and source-generated
System.Text.Json serialization survive Native AOT:

- tests/JustSaying.AotTest: a net10.0 PublishAot exe that runs an in-process
  round trip against LocalSqsSnsMessaging's in-memory AWS bus, registering a
  source-generated SystemTextJsonSerializationFactory. Notably it needs no
  [UnconditionalSuppressMessage] around AddJustSaying - the core is AOT-clean.
- A native-aot (linux) CI job that publishes the app to a native binary and
  runs it; a non-zero exit fails the build.

Verified locally: native publish (osx-arm64) produces a clean binary - the only
IL2104/IL3053 warnings come from the test-only LocalSqsSnsMessaging dependency
(demoted to warnings) - and the binary runs green under Native AOT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1.0.1 ships fully trim/AOT-annotated, so the test-only IL2104/IL3053 warnings
are gone. Drop the now-unnecessary WarningsNotAsErrors demotion from the AOT
test project - it is strict again, the native publish is completely
warning-free, and the binary still runs green under Native AOT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a source-generated ApplicationJsonContext and register a
SystemTextJsonSerializationFactory built from it (and plug the same context into
ASP.NET's minimal-API JSON pipeline) so JustSaying serialization in the sample
uses source generation rather than reflection - the pattern an app needs to be
Native-AOT-publishable.

PublishAot is intentionally not set on this sample: it depends on Aspire
ServiceDefaults / OpenTelemetry / Scalar, whose native-AOT readiness is out of
JustSaying's scope. The dedicated tests/JustSaying.AotTest already proves the
library itself runs as a native-AOT binary.

Sample builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes the STScJ typo and replaces the comment-only opt-in example with real
registration for both containers, spelling out the ordering each one needs:
Microsoft DI registers the default with TryAddSingleton so yours must come
first, StructureMap takes the last registration so yours must come last. Also
drops the reference to a fluent WithMessageBodySerializer hook, which does not
exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.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.

2 participants