Skip to content

Native AOT & trimming

Reflection-based assembly scanning is what makes most mediators unusable under trimming. Mediant ships a source generator that does the same work at compile time.

Terminal window
dotnet add package Mediant.SourceGenerator
// Generated: registers every handler and precomputes Send/Publish/Stream dispatch.
builder.Services.AddMediantGenerated();

That replaces AddMediant(cfg => cfg.RegisterServicesFromAssembly(...)). No assembly scanning, no runtime code generation, nothing for the trimmer to guess at.

What you get

  • Every ICommandHandler, IQueryHandler, IRequestHandler, INotificationHandler and IStreamRequestHandler in the compilation is discovered and registered.
  • Dispatch tables for Send, Publish and CreateStream are emitted as plain switch code.
  • The core Mediant assembly is marked IsAotCompatible, and its dispatch path is verified trim- and AOT-clean by the analyzers.

The reflection path still works — keep AddMediant(...) for ordinary JIT scenarios if you prefer it. Under trimming or Native AOT, use AddMediantGenerated().

JSON serialization

Three features serialize to JSON: the caching behavior, the idempotency store, and the outbox. Under AOT they need a JsonSerializerContext rather than reflection-based System.Text.Json.

[JsonSerializable(typeof(GetOrderById))]
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(OrderCreatedEvent))]
internal partial class AppJsonContext : JsonSerializerContext;

Then pass its options wherever they’re accepted:

builder.Services.AddMediantAllBehaviors(opts =>
{
opts.SerializerOptions = AppJsonContext.Default.Options;
});

Register a type for every request that is [Cacheable] or [Idempotent], every response those return, and every notification that goes through the outbox.

Publishing

Terminal window
dotnet publish -c Release -r linux-x64 -p:PublishAot=true

Then check the build output. Trim and AOT warnings are the contract: if Mediant produces one, it is a bug worth reporting.

<PropertyGroup>
<PublishAot>true</PublishAot>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>

What doesn’t work

cfg.AddOpenBehavior(typeof(MyBehavior<,>)) closes an open generic at runtime. That is fine under JIT and under the trimmer with the generator (the closed types are rooted), but if you construct handler or behavior types dynamically yourself, the trimmer cannot see them. Keep that reflection out of the hot path, or root the types explicitly.