Loading...
Loading...
Native AOT, trimming, and single-file publishing for .NET 10: PublishAot, IsAotCompatible analyzers, source-generated JSON/regex/logging/config binding, fixing IL2xxx/IL3xxx trim and AOT warnings, per-RID publishing, and when NOT to use AOT. Load when the user mentions "Native AOT", "PublishAot", "trimming", "PublishTrimmed", "single-file", "IL2026", "IL3050", "trim warning", "startup time", "self-contained", or "JsonSerializerContext".
npx skill4agent add codewithmukesh/dotnet-claude-kit native-aotMissingMethodExceptiondotnet rundotnet rundotnet publishReflection.Emit<!-- App (executable) project -->
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PublishAot>true</PublishAot> <!-- also turns on AOT analyzers during build -->
<InvariantGlobalization>true</InvariantGlobalization> <!-- drop ICU if you don't need cultures -->
<OptimizationPreference>Size</OptimizationPreference> <!-- or Speed -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Every class library the app references -->
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible> <!-- implies IsTrimmable + trim/AOT/single-file analyzers -->
</PropertyGroup>dotnet publish -c Release -r linux-x64 # one RID per publishPublishAotIsAotCompatibleclangzlibpublic sealed record OrderDto(Guid Id, string Customer, decimal Total, DateTimeOffset CreatedAt);
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(OrderDto))]
[JsonSerializable(typeof(List<OrderDto>))]
internal sealed partial class AppJsonContext : JsonSerializerContext;
// Usage — pass the generated JsonTypeInfo, never a bare Type
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.OrderDto);
var orders = await JsonSerializer.DeserializeAsync(stream, AppJsonContext.Default.ListOrderDto, ct);// ASP.NET Core minimal APIs: register the context once
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));System.Text.JsonList<OrderDto>CreateSlimBuilderPublishAotinternal static partial class Patterns
{
[GeneratedRegex(@"^ORD-\d{6}$", RegexOptions.CultureInvariant)]
internal static partial Regex OrderNumber { get; }
}
internal static partial class Log
{
[LoggerMessage(Level = LogLevel.Information, Message = "Imported {Count} orders in {ElapsedMs} ms")]
internal static partial void Imported(ILogger logger, int count, long elapsedMs);
}
[OptionsValidator]
internal sealed partial class ImportOptionsValidator : IValidateOptions<ImportOptions>;
public sealed class ImportOptions
{
[Range(1, 10_000)] public int BatchSize { get; set; } = 500;
}<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>[GeneratedRegex]RegexOptions.Compiled[LoggerMessage]BindGet<T>Configure<T>[OptionsValidator]// Propagate the requirement to callers instead of hiding it
[RequiresUnreferencedCode("Loads plugin types by name; not trim-safe.")]
[RequiresDynamicCode("Creates generic types at runtime.")]
public static object CreatePlugin(string typeName) => Activator.CreateInstance(Type.GetType(typeName)!)!;
// Or tell the trimmer exactly what to keep
public static T Create<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>()
where T : new() => new();
public static object CreateFrom(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) =>
Activator.CreateInstance(type)!;[RequiresUnreferencedCode][RequiresDynamicCode][DynamicallyAccessedMembers][UnconditionalSuppressMessage]<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<PublishTrimmed>true</PublishTrimmed> <!-- optional; requires the same trim discipline -->
</PropertyGroup>// BAD in single-file: Assembly.Location is empty (IL3000)
var dir = Path.GetDirectoryName(typeof(Program).Assembly.Location);
// GOOD
var dir = AppContext.BaseDirectory;strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- run: dotnet publish src/Contoso.Cli -c Release -o out
- run: ./out/contoso --version && ./out/contoso import --input samples/orders.csv --dry-run<!-- BAD — hides the crash until production -->
<NoWarn>$(NoWarn);IL2026;IL3050</NoWarn>
<TrimMode>partial</TrimMode>// GOOD — fix the cause: generated serializer instead of reflection
JsonSerializer.Serialize(order, AppJsonContext.Default.OrderDto);// BAD — IL2026/IL3050: needs runtime reflection over OrderDto
var json = JsonSerializer.Serialize(order);
var dto = await response.Content.ReadFromJsonAsync<OrderDto>(ct);
// GOOD — JsonTypeInfo overloads
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.OrderDto);
var dto = await response.Content.ReadFromJsonAsync(AppJsonContext.Default.OrderDto, ct);// BAD — trimmer removes "unused" types; GetTypes() finds nothing at runtime
foreach (var t in typeof(Program).Assembly.GetTypes().Where(t => t.IsAssignableTo(typeof(ICommandHandler))))
services.AddTransient(typeof(ICommandHandler), t);
// GOOD — explicit registration (or a source generator that emits it)
services.AddTransient<ICommandHandler, ImportHandler>();
services.AddTransient<ICommandHandler, ExportHandler>();RegexOptions.Compiled// BAD — needs Reflection.Emit; under AOT it falls back to slower interpretation
private static readonly Regex Sku = new(@"^[A-Z]{3}-\d+$", RegexOptions.Compiled);
// GOOD
[GeneratedRegex(@"^[A-Z]{3}-\d+$")]
private static partial Regex Sku { get; }| Scenario | Recommendation |
|---|---|
| CLI tool shipped to non-.NET users | Native AOT per RID |
| Native AOT is already the default for file-based apps |
| Serverless / scale-to-zero / many small containers | Native AOT (fast cold start, low memory); see |
| Avalonia desktop app | Native AOT viable with compiled bindings + trim-safe view mapping |
| WPF or WinForms | No trimming/AOT support; use ReadyToRun + single-file for startup |
| Long-running, high-throughput API | Usually JIT (Dynamic PGO wins at steady state); measure before switching |
| EF Core data access | EF Core AOT (precompiled queries) is still experimental; stay JIT for EF-heavy apps |
| MVC controllers, SignalR-heavy, Blazor Server | Not AOT targets; use minimal APIs if AOT is required |
Plugins / | Not possible under AOT; use JIT |
| One-exe download, AOT-incompatible deps | |
| Library meant for AOT consumers | |
| Dependency emits trim warnings | Replace it, or isolate behind |