Skip to content

Support Native AOT - #1348

Closed
hwoodiwiss wants to merge 37 commits into
mainfrom
hwoodiwiss-native-aot
Closed

hwoodiwiss wants to merge 37 commits into
mainfrom
hwoodiwiss-native-aot

Conversation

@hwoodiwiss

@hwoodiwiss hwoodiwiss commented Feb 17, 2024

Copy link
Copy Markdown
Member

This PR is a work in progress to get Native AoT working. Posting early, as I think it'll be good to get eyes on early.

AoT support as added by using a new generic serializer type and serialization factory to create it.

fixes #1347 (eventually)

Things still to do:

  • Work out properly how to get JsonSerializerOptions injected, not sure we should be adding a new dependency on Microsoft.Extensions.Options
  • Add better behaviour switching for .NET 8 builds, currenlty, .NET 8 builds will default to AOT compatible behaviour.
  • Cleanup changes made for local testing
  • Add general test coverage
  • Add some sort of testing to validate the case of AOT compiled builds
  • Bump Version (Major?)

This commit gets AoT working to the point that I can actually test it, and see it work AOT locally
@hwoodiwiss hwoodiwiss changed the title Support Native Aot Support Native AOT Feb 17, 2024
@hwoodiwiss

Copy link
Copy Markdown
Member Author

This might be a bit cleaner to integrate if we look to add .NET 8 as a first-class TFM first

@codecov

codecov Bot commented Feb 17, 2024

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.51%. Comparing base (41a351d) to head (7c27e9e).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...alization/SystemTextJsonMessageBodySerializer`1.cs 25.00% 4 Missing and 2 partials ⚠️
...g/Fluent/ServiceResolver/DefaultServiceResolver.cs 33.33% 1 Missing and 1 partial ⚠️
...njection.Microsoft/IServiceCollectionExtensions.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1348      +/-   ##
==========================================
- Coverage   78.64%   78.51%   -0.14%     
==========================================
  Files         149      150       +1     
  Lines        3939     3952      +13     
  Branches      652      656       +4     
==========================================
+ Hits         3098     3103       +5     
- Misses        543      548       +5     
- Partials      298      301       +3     
Flag Coverage Δ
linux 78.51% <70.00%> (-0.14%) ⬇️
macos 60.96% <72.41%> (+<0.01%) ⬆️
windows 60.91% <72.41%> (+<0.01%) ⬆️

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.

@hwoodiwiss hwoodiwiss added enhancement dependencies Pull requests that update a dependency file .NET Pull requests that update .net code labels Feb 17, 2024
@martincostello

Copy link
Copy Markdown
Contributor
  • Add some sort of testing to validate the case of AOT compiled builds

We use this test project in Polly. In theory compiling it with the assemblies trim-rooted is enough to flush out issues that the analysers can't detect.

Bump Version (Major?)

Unless we have to break stuff, I'd class it just as a minor. I'd a minor planned for #1335 once I've validated it internally, so depending on how long both take to complete, we could have either an 8.2 then an 8.3, or ship them together as 8.2.

Comment thread src/JustSaying/AwsTools/MessageHandling/PublishException.cs

public override string ToString()
#if NET8_0_OR_GREATER
=> System.Text.Json.JsonSerializer.Serialize(this, JustSayingSerializationContext.Default.RedrivePolicy);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this might be problematic for AoT in case it's a derived type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, that's a good point, I think it'll end up serializing the base fields, but none added by the derived type.
At the moment, RedrivePolicy is only used internally, and I can't see a way that an API user could derive it and pass it through to any internal usage, but I could be missing it.

If that is the case, I don't think it would be a bad idea to internal sealed it.

Comment on lines +50 to +56
if (RuntimeFeature.IsDynamicCodeSupported)
{
#pragma warning disable IL2026
#pragma warning disable IL3050
return new NewtonsoftSerializationFactory();
#pragma warning restore
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Slightly surprised it isn't smart enough to not warn about this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll check, I think I had the check backwards when I added the #pragma warning disable's

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, even with that fixed, it still warns.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've found an issue related to this: dotnet/runtime#97273, looks like the supression in runtime checks just didn't make it into .NET 8, not sure if they plan to backport either.

It also looks like there are plans for a wider design proposal to support feature checks suppressing analyzers: dotnet/runtime#96859

Comment thread src/JustSaying/JustSayingSerializationContext.cs
Comment on lines +73 to +74
var dataType = obj.Value.GetProperty("Type").GetString();
var dataValue = obj.Value.GetProperty("Value").GetString();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should make these defensive against the possibility of the properties not being there so we don't NRE.

Comment thread src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer`1.cs Outdated

namespace JustSaying.Messaging.MessageSerialization;

public class TypedSystemTextJsonSerializationFactory(JsonSerializerOptions options) : IMessageSerializationFactory

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not the biggest fan of the Typed prefix.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, same, I was at the "get it working at any cost" stage by then. I'll try and think of something a bit sleaker, or merge this back in with the current STJSerializationFactory, and have it use the typed serializer when dynamic code isn't supported.

If we go this way, in the next breaking version, I think it'll make sense to deprecate the non-typed serializer, and to that end, maybe decorate it now.

var typeInfo = options.GetTypeInfo(typeof(T));
if (typeInfo is not JsonTypeInfo<T> genericTypeInfo)
{
throw new JsonException($"Could not find type info for the specified type {typeof(T).Name}");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This could probably be a bit more informative.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It ends up being redundant anyway tbh, GetTypeInfo(Type) throws if it can't find the type info anyway.

@hwoodiwiss

Copy link
Copy Markdown
Member Author

Unless we have to break stuff, I'd class it just as a minor. I'd a minor planned for #1335 once I've validated it internally, so depending on how long both take to complete, we could have either an 8.2 then an 8.3, or ship them together as 8.2.

That makes sense. It makes sense to me that we should add the net8.0 tfm before this, in part just to reduce the overall size/complexity of this change, so maybe we could do:
8.2 - Batch Publish and net8.0 tfm
8.3 - AOT Support if it doesn't require breaking changes.

(Pushing rather than stashing)
@hwoodiwiss hwoodiwiss mentioned this pull request Feb 17, 2024
@slang25

slang25 commented Feb 18, 2024

Copy link
Copy Markdown
Contributor

This is looking very nice 🙂 I had looked at this a while back in a local branch and the thing that I was concerned about was the JsonStringEnumConverter behaviour, which in an AOT environment will need to be added to the source generated serializer per-enum using JsonStringEnumConverter<TEnum>, which is fine but I think something that will be easily missed by consumers.

@hwoodiwiss

Copy link
Copy Markdown
Member Author

This is looking very nice 🙂 I had looked at this a while back in a local branch and the thing that I was concerned about was the JsonStringEnumConverter behaviour, which in an AOT environment will need to be added to the source generated serializer per-enum using JsonStringEnumConverter<TEnum>, which is fine but I think something that will be easily missed by consumers.

Yeah, I'd agree, I think this is a known enough sharp edge of AOT compilation, that we could just guard the addition of the default JsonStringEnumConverter with RuntimeFeature.IsDynamicCodeSupported, I'm not sure if there's a better way that we could surface a warning around this to API consumers.

@slang25

slang25 commented Feb 18, 2024

Copy link
Copy Markdown
Contributor

Another thought I had while browsing this change, now might be the time to create separate interfaces and implementation for the "message envelope" and message serializer/deserializer.

Comment thread src/JustSaying/Messaging/MessageSerialization/JustSayingJsonSerializerOptions.cs Outdated
@hwoodiwiss

Copy link
Copy Markdown
Member Author

Another thought I had while browsing this change, now might be the time to create separate interfaces and implementation for the "message envelope" and message serializer/deserializer.

Yeah, I had originally thought this change would require doing that anyway but found a way to avoid it.
I think I'd prefer a v1 of AoT support to not alter the public API, at the cost of ergonomics for the AoT use-case, then we can look at building first-class support as part of the API changes that come for v8.

[RequiresUnreferencedCode(Constants.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(Constants.SerializationDynamicCodeMessage)]
#endif
public class NewtonsoftSerializer : IMessageSerializer

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is an assumption at the moment. Newtonsoft.Json isn't currently annotated for AOT/Trimming compatibility.

Comment thread src/JustSaying/Extensions/JsonSerializerOptionsExtensions.cs Outdated
Comment thread src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer.cs Outdated
hwoodiwiss and others added 18 commits March 3, 2024 23:22
TODO: Make test names/namespaces not the worst
# Conflicts:
#	Directory.Packages.props
#	samples/src/JustSaying.Sample.Restaurant.OrderingApi/Program.cs
#	src/JustSaying.Extensions.DependencyInjection.Microsoft/IServiceCollectionExtensions.cs
#	src/JustSaying/AwsTools/QueueCreation/RedrivePolicy.cs
#	src/JustSaying/Fluent/AccountAddressProvider.cs
#	src/JustSaying/Fluent/QueueAddress.cs
#	src/JustSaying/JustSayingBus.cs
#	src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializationFactory.cs
#	src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializer.cs
#	src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializationFactory.cs
#	src/JustSaying/Messaging/MessageSerialization/SystemTextJsonSerializer.cs
#	src/JustSaying/PublicAPI/net461/PublicAPI.Unshipped.txt
#	src/JustSaying/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
#	tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/DealingWithPotentiallyMissingConversation.cs
#	tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/WhenSerializingAndDeserializing.cs
#	tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/WhenUsingCustomSettings.cs

Co-authored-by: hwoodiwiss <2156707+hwoodiwiss@users.noreply.github.com>
The merge from main removed IMessageSerializer (replaced by IMessageBodySerializer)
but left the AOT branch's SystemTextJsonSerializer<T> and its tests behind.
Drop the orphaned files, port the unique factory test to use
SystemTextJsonMessageBodySerializer<T>, and update doc references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gate the reflection-based JSON paths in SystemTextJsonMessageBodySerializer<T>
and the default JsonStringEnumConverter on RuntimeFeature.IsDynamicCodeSupported,
falling back to JsonTypeInfo<T> from the supplied options. Route RedrivePolicy
through JustSayingSerializationContext on net8.0 so it no longer triggers
IL2026/IL3050. Migrate JsonSerializerOptionsExtensionsTests to TUnit and
suppress IL2026 in the OrderingApi sample (the AOT-safe configuration entry
is still listed as TODO in the PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix an unbalanced #pragma in DefaultServiceResolver where IL3050 was
being disabled twice instead of restored, leaking the suppression into
the rest of the file. Reword the accompanying error to talk about
dynamic code rather than PublishTrimmed, since the actual gate is
RuntimeFeature.IsDynamicCodeSupported. Drop the unused
Microsoft.Extensions.Options package reference and the redundant
IsAotCompatible property (already set in Directory.Build.props), and
remove the unused MessagingJsonSerializerOptions placeholder from the
OrderingApi sample.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump Newtonsoft.Json to 13.0.4 so it carries trim annotations, then
propagate the resulting AOT/trim warnings through JustSaying's
Newtonsoft surface: annotate the parameterless ctor of
NewtonsoftMessageBodySerializer<T> with [RequiresUnreferencedCode] /
[RequiresDynamicCode], and suppress IL2026 / IL3050 inside Serialize,
Deserialize, and NewtonsoftSerializationFactory.GetSerializer<T> with
the justification that the caller has already opted into Newtonsoft by
selecting it. Drop the now-stale 13.0.3 VersionOverride from the test
projects. Suppress IL3053 in the OrderingApi sample csproj for the
remaining AOT warnings that originate inside Newtonsoft itself.

Verified: dotnet publish -c Release -r osx-arm64 produces a 30 MB
native binary that boots, configures JustSaying, and wires up its SQS
subscriptions and SNS publishers without any runtime AOT errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Register a SystemTextJsonSerializationFactory backed by
ApplicationJsonContext (System.Text.Json source-gen) before
AddJustSaying so the default Newtonsoft factory is never selected.
Move the AddJustSaying call into a static helper marked
[UnconditionalSuppressMessage] for IL2026, with a justification
explaining that we have replaced the unsafe factory above. This drops
the blanket IL2026 NoWarn from the csproj, leaving only the warnings
that originate inside Newtonsoft.Json's own assembly (IL2104, IL3053).

Verified end-to-end: native AOT publish completes, the binary boots,
JustSaying configures itself, Kestrel binds and the host reports
"Application started" with no AOT-related runtime errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Minimal console app modelled on Polly.AotTest that wires JustSaying
through DI with the AOT-safe SystemTextJson source-gen factory, builds
the service provider, and exits. PublishAot is enabled, so any future
change that breaks Native AOT compatibility shows up as a publish-time
ilc failure rather than a runtime surprise. Not yet wired into CI;
that is a separate decision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .NET 10 SDK analyzer recognises RuntimeFeature.IsDynamicCodeSupported
as a feature switch (dotnet/runtime#97273), so IL2026 / IL3050 no longer
fire under the Newtonsoft branch and the #pragma disables are dead
suppressions. Verified the build is still clean and AOT publish still
succeeds without them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three internal helpers added by earlier branch work were never wired
up: Constants.cs (two identical strings, never referenced),
SqsMessageEnvelope (registered in JustSayingSerializationContext but
no code serializes or deserializes it — the SNS unwrap still goes
through JsonNode.Parse in InboundMessageConverter), and
JsonElementExtensions.TryGetStringProperty (zero callers, with a
JsonValueKind.Null branch that GetString() makes unreachable).

Also drop two stale CS0618 pragma suppressions that were left over
from when RedrivePolicy was briefly [Obsolete] before being reworked
to internal sealed — the warning they were silencing can no longer
fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Newtonsoft serializer surface was inconsistently annotated:
the parameterless NewtonsoftMessageBodySerializer<T> ctor warned, but
NewtonsoftSerializationFactory(JsonSerializerSettings) and the
parameterized serializer ctor were silent, so user code that did
`new NewtonsoftSerializationFactory().GetSerializer<T>()` in a
PublishAot=true project compiled clean and failed at runtime.

Add [RequiresUnreferencedCode] / [RequiresDynamicCode] to:
 - NewtonsoftSerializationFactory's constructor (converted from a
   primary ctor for the attribute targets)
 - NewtonsoftMessageBodySerializer<T>(JsonSerializerSettings)
 - JustSayingRegistry's ctor and all four AddJustSaying overloads on
   StructureMap's ConfigurationExpressionExtensions, since
   StructureMap is reflection-heavy and not AOT-safe at all
 - [RequiresDynamicCode] on the Microsoft DI and AWS extensions'
   AddJustSaying / AddJustSayingWithAwsConfig overloads, which were
   already [RequiresUnreferencedCode] but missed the AOT contract,
   so ilc still complained at publish time.

Re-add the IL2026 / IL3050 suppressions in DefaultServiceResolver
that I dropped earlier — RuntimeFeature.IsDynamicCodeSupported is
treated as a feature switch only for some shapes; here it doesn't
suppress, and the warning re-surfaces once the factory ctor is
annotated. Update the AotTest and OrderingApi sample suppressions to
cover both IL2026 and IL3050 to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier sample wire-up plumbed ApplicationJsonContext into
JustSaying's IMessageBodySerializationFactory, which kept the
messaging path AOT-safe. ASP.NET Core minimal APIs use a separate
JsonSerializerOptions instance owned by Microsoft.AspNetCore.Http.Json,
so request body binding for POST api/orders and POST api/multi-orders
was still going through the reflection-based DefaultJsonTypeInfoResolver
— which native AOT trims out, so any POST throws once published.

Insert ApplicationJsonContext at the head of the HTTP JSON
TypeInfoResolverChain via ConfigureHttpJsonOptions, and add
[JsonSerializable(typeof(IReadOnlyCollection<CustomerOrderModel>))]
to the source-gen context for the multi-order endpoint.

Verified end-to-end: AOT-published binary boots, both POST endpoints
deserialize their request bodies correctly under native AOT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace `NoWarn` for IL2104/IL3053 with `WarningsNotAsErrors` in the
OrderingApi sample and AotTest projects. The previous suppression hid
these warnings from any assembly forever; this keeps the publish log
showing which assembly produced them, so a new non-AOT-safe transitive
dependency would surface as an obvious warning rather than getting
silently swallowed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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 early Native AOT support across JustSaying by introducing/using source-generated System.Text.Json paths where reflection-based serialization is not supported, plus a dedicated AOT smoke-test project and sample wiring changes.

Changes:

  • Add AOT-safe System.Text.Json serialization flow (factory + serializer updates) and a shared source-gen JsonSerializerContext for core internal types.
  • Annotate reflection/dynamic-code-dependent entry points (e.g., Newtonsoft + StructureMap + DI extensions) with trimming/AOT attributes and add runtime gating where needed.
  • Add an AOT publish smoke test project and update the Restaurant sample to use source-gen serialization and enable AOT publish settings.

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/JustSaying.UnitTests/Messaging/Serialization/SystemTextJson/WhenAskingForANewSerializer.cs New unit test validating the STJ serialization factory returns a serializer.
tests/JustSaying.UnitTests/JustSaying.UnitTests.csproj Remove Newtonsoft version override to rely on central package management.
tests/JustSaying.UnitTests/Extensions/JsonSerializerOptionsExtensionsTests.cs New unit tests for JsonSerializerOptions.GetTypeInfo<T> helper behavior.
tests/JustSaying.UnitTests/AwsTools/QueueCreation/WhenSerializingRedrivePolicy.cs Formatting-only fix in unit test file.
tests/JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj Remove Newtonsoft version override to rely on central package management.
tests/JustSaying.Extensions.DependencyInjection.StructureMap.Tests/JustSaying.Extensions.DependencyInjection.StructureMap.Tests.csproj Remove Newtonsoft version override to rely on central package management.
tests/JustSaying.AotTest/Program.cs New Native AOT smoke test that builds a DI container with AOT-safe serializer factory.
tests/JustSaying.AotTest/JustSaying.AotTest.csproj New AOT publish project (net10) with warning demotions for known transitive Newtonsoft warnings.
src/JustSaying/Messaging/MessageSerialization/SystemTextJsonMessageBodySerializer`1.cs Add dynamic-code vs AOT branching to prefer source-gen metadata when dynamic code is unavailable.
src/JustSaying/Messaging/MessageSerialization/SystemTextJsonMessageBodySerializer.cs Refactor default STJ options creation and conditionally add enum converter based on dynamic-code support.
src/JustSaying/Messaging/MessageSerialization/NewtonsoftSerializationFactory.cs Add trimming/AOT annotations and internal caching refactor for Newtonsoft factory.
src/JustSaying/Messaging/MessageSerialization/NewtonsoftMessageBodySerializer`1.cs Add trimming/AOT annotations and suppressions around Newtonsoft serialization calls.
src/JustSaying/Messaging/MessageSerialization/MessageFormatNotSupportedException.cs Minor preprocessor/spacing cleanup around serialization ctor.
src/JustSaying/JustSayingSerializationContext.cs New internal STJ source-gen context for internal serialization needs (net8+).
src/JustSaying/Fluent/ServiceResolver/DefaultServiceResolver.cs Gate default Newtonsoft factory resolution behind dynamic-code availability (net8+).
src/JustSaying/Extensions/JsonSerializerOptionsExtensions.cs Add net8+ internal helper to strongly-type JsonSerializerOptions.GetTypeInfo.
src/JustSaying/AwsTools/QueueCreation/RedrivePolicy.cs Use source-gen context for STJ serialization/deserialization on net8+.
src/JustSaying/AwsTools/MessageHandling/SnsPolicyBuilder.cs Use source-gen context for STJ serialization of account IDs on net8+.
src/JustSaying.Extensions.DependencyInjection.StructureMap/JustSayingRegistry.cs Add trimming/AOT annotations for StructureMap registry constructor (net8+).
src/JustSaying.Extensions.DependencyInjection.StructureMap/ConfigurationExpressionExtensions.cs Add trimming/AOT annotations for StructureMap extension methods (net8+).
src/JustSaying.Extensions.DependencyInjection.Microsoft/IServiceCollectionExtensions.cs Add trimming/AOT annotations for DI entry points and annotate handler type for constructor preservation (net8+).
src/JustSaying.Extensions.Aws/IServiceCollectionExtensions.cs Add trimming/AOT annotations for AWS config DI helpers (net8+).
samples/src/JustSaying.Sample.Restaurant.OrderingApi/appsettings.json Update local AWS service URL default (LocalStack port).
samples/src/JustSaying.Sample.Restaurant.OrderingApi/Program.cs Wire AOT-safe STJ serialization (JustSaying + minimal API) and add suppression wrapper for AOT warnings.
samples/src/JustSaying.Sample.Restaurant.OrderingApi/OrderingApi.http Add HTTP file for local request testing.
samples/src/JustSaying.Sample.Restaurant.OrderingApi/JustSaying.Sample.Restaurant.OrderingApi.csproj Enable AOT publish and demote specific IL warnings; ensure appsettings is published.
samples/src/JustSaying.Sample.Restaurant.OrderingApi/ApplicationJsonContext.cs Add STJ source-gen context for sample API models/events.
JustSaying.slnx Add the new JustSaying.AotTest project to the solution.
Directory.Packages.props Bump Newtonsoft.Json to 13.0.4 centrally.
Directory.Build.props Add net8+ global using for CodeAnalysis attributes and set IsAotCompatible property.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1 to 4
using System.Text.Json;
using Amazon;
using JustSaying.Messaging.MessageSerialization;

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

using JustSaying.Messaging.MessageSerialization; appears to be unused in this file, which will trigger CS8019 and fail the build because warnings are treated as errors. Remove the unused using directive (or reference a type from that namespace if it was intended).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to 3
using System.Runtime.CompilerServices;
using JustSaying.AwsTools;
using JustSaying.Messaging.Compression;

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

using System.Runtime.CompilerServices; is only needed for RuntimeFeature inside the #if NET8_0_OR_GREATER block. On net462/netstandard2.0 builds this using becomes unused, which will raise CS8019 and fail the build (warnings are treated as errors). Wrap the using in #if NET8_0_OR_GREATER or remove it and fully-qualify RuntimeFeature inside the conditional block.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +51
var jsonTypeInfo = _options.GetTypeInfo<T>();
return JsonSerializer.Serialize((T)message, jsonTypeInfo);

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

In the AOT path, serialization uses JsonTypeInfo<T> and casts message to T. This changes behavior vs the existing runtime-type serialization (message.GetType()): derived message instances published via a base T will be serialized as the base type (potentially dropping derived properties / polymorphism settings), and it can also throw if message isn't assignable to T. To keep behavior consistent across dynamic/AOT, consider resolving JsonTypeInfo for the runtime type (e.g., via options.GetTypeInfo(message.GetType())) and serializing with that.

Suggested change
var jsonTypeInfo = _options.GetTypeInfo<T>();
return JsonSerializer.Serialize((T)message, jsonTypeInfo);
var jsonTypeInfo = _options.GetTypeInfo(message.GetType());
return JsonSerializer.Serialize(message, jsonTypeInfo);

Copilot uses AI. Check for mistakes.
- SnsPolicyBuilder: drop unused JustSaying.Messaging.MessageSerialization
  using (JustSayingSerializationContext is in the parent JustSaying namespace)
- DefaultServiceResolver: guard System.Runtime.CompilerServices using
  with NET8_0_OR_GREATER since RuntimeFeature is only referenced in that
  branch, otherwise the using is dead on net462/netstandard2.0
- DI Microsoft IServiceCollectionExtensions: the Requires* attribute
  messages referenced IMessageSerializationFactory but the registered
  service is IMessageBodySerializationFactory

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

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

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


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Directory.Build.props
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">
<Using Include="System.Diagnostics.CodeAnalysis" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

<IsAotCompatible>true</IsAotCompatible> is being applied to every project targeting net8.0+. Some shipped projects in this repo are explicitly not AOT-compatible (e.g. the StructureMap integration, and the default Newtonsoft-based configuration paths). Consider scoping IsAotCompatible to only the projects/TFMs that are actually AOT-safe, or deferring enabling it until the repo’s net8.0 assemblies meet the SDK’s expectations for this flag.

Suggested change
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0')) and '$(EnableAotCompatibility)' == 'true'">

Copilot uses AI. Check for mistakes.
private static string SerializeAccountIds(IReadOnlyCollection<string> accountIds)
{
#if NET8_0_OR_GREATER
return JsonSerializer.Serialize(accountIds, JustSayingSerializationContext.Default.IReadOnlyCollectionString);

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

JustSayingSerializationContext is declared in the JustSaying namespace, but this file is in JustSaying.AwsTools.MessageHandling and references it without a using JustSaying; or fully-qualifying the type. This will fail to compile for net8.0+ builds. Add using JustSaying; or change the reference to JustSaying.JustSayingSerializationContext.

Suggested change
return JsonSerializer.Serialize(accountIds, JustSayingSerializationContext.Default.IReadOnlyCollectionString);
return JsonSerializer.Serialize(accountIds, JustSaying.JustSayingSerializationContext.Default.IReadOnlyCollectionString);

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +24
#if NET8_0_OR_GREATER
return JsonSerializer.Serialize(this, JustSayingSerializationContext.Default.RedrivePolicy);
#else

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

JustSayingSerializationContext lives in the JustSaying namespace, but RedrivePolicy is in JustSaying.AwsTools.QueueCreation and references JustSayingSerializationContext without importing/qualifying it. This will not compile for net8.0+ builds. Add using JustSaying; or fully-qualify the type name.

Copilot uses AI. Check for mistakes.
slang25 and others added 3 commits May 28, 2026 00:13
…AOT attributes

The default JsonSerializerOptions have no TypeInfoResolver and fall back to
reflection-based metadata, which is incompatible with trimming and Native AOT.
Mark the parameterless constructor with RequiresUnreferencedCode and
RequiresDynamicCode so callers see the warning and can switch to the overload
that accepts a source-generated context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	Directory.Packages.props
#	tests/JustSaying.IntegrationTests/JustSaying.IntegrationTests.csproj
Convert JustSaying.AotTest from a container-build smoke test into a TUnit
test that exercises the full publish -> subscribe -> handle round trip
against the in-memory LocalSqsSnsMessaging bus, so a pass proves the
configuration, message-pump and source-generated System.Text.Json
serialization paths all survive Native AOT with no external AWS services.

The MTP code-coverage extension is not AOT compatible (loaded by
reflection), so its self-registration hook is dropped for this project.

Add a native-aot Linux CI job that publishes the project with
PublishAot for linux-x64 and runs the resulting native binary; a
non-zero exit fails the build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@slang25

slang25 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Picking this up in #2183

@slang25 slang25 closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file enhancement .NET Pull requests that update .net code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native AOT Compatibility

5 participants