skills/rudironsoni/dotnet-harness-plugin/dotnet-aot-architecture

dotnet-aot-architecture

SKILL.md

dotnet-aot-architecture

AOT-first application design patterns for .NET 8+: preferring source generators over reflection, explicit DI registration over assembly scanning, AOT-safe serialization choices, library compatibility assessment, and factory patterns replacing Activator.CreateInstance.

Version assumptions: .NET 8.0+ baseline. Patterns apply to all AOT-capable project types (console, ASP.NET Core Minimal APIs, worker services).

Scope

  • Source generator replacements for reflection patterns
  • AOT-safe DI patterns (explicit registration, keyed services)
  • Serialization choices for AOT (STJ source gen, Protobuf, MessagePack)
  • Factory patterns replacing Activator.CreateInstance
  • Library compatibility assessment for AOT
  • AOT application architecture template

Out of scope

  • Native AOT publish pipeline and MSBuild configuration -- see [skill:dotnet-native-aot]
  • Trim-safe library authoring and annotations -- see [skill:dotnet-trimming]
  • WASM AOT compilation -- see [skill:dotnet-aot-wasm]
  • MAUI-specific AOT -- see [skill:dotnet-maui-aot]
  • Source generator authoring (Roslyn API) -- see [skill:dotnet-csharp-source-generators]
  • DI container internals -- see [skill:dotnet-csharp-dependency-injection]
  • Serialization depth -- see [skill:dotnet-serialization]

Cross-references: [skill:dotnet-native-aot] for the AOT publish pipeline, [skill:dotnet-trimming] for trim annotations and library authoring, [skill:dotnet-serialization] for serialization patterns, [skill:dotnet-csharp-source-generators] for source gen mechanics, [skill:dotnet-csharp-dependency-injection] for DI fundamentals, [skill:dotnet-containers] for runtime-deps deployment, [skill:dotnet-native-interop] for general P/Invoke patterns and marshalling.


Source Generators Over Reflection

The primary AOT enabler is replacing runtime reflection with compile-time source generation. Source generators produce code at build time that the AOT compiler can analyze and include.

Key Source Generator Replacements

Reflection Pattern Source Generator / AOT-Safe Alternative Library
JsonSerializer.Deserialize<T>() [JsonSerializable] context System.Text.Json (built-in)
Activator.CreateInstance<T>() Factory pattern with explicit new Manual
Type.GetProperties() for mapping [Mapper] attribute Mapperly
Regex pattern compilation [GeneratedRegex] attribute Built-in (.NET 7+)
ILogger.Log(...) with string interpolation [LoggerMessage] attribute Microsoft.Extensions.Logging
Assembly scanning for DI Explicit services.Add*() Manual
[DllImport] P/Invoke [LibraryImport] Built-in (.NET 7+)
AutoMapper CreateMap<>() [Mapper] source gen Mapperly

Example: Migrating to Source Gen


// BEFORE: Reflection-based (breaks under AOT)
var logger = loggerFactory.CreateLogger<OrderService>();
logger.LogInformation("Order {OrderId} created for {Customer}", order.Id, order.CustomerId);

// AFTER: Source-generated (AOT-safe, zero-alloc)
public partial class OrderService
{
    [LoggerMessage(Level = LogLevel.Information,
        Message = "Order {OrderId} created for {Customer}")]
    private static partial void LogOrderCreated(
        ILogger logger, int orderId, string customer);
}

// Usage:
LogOrderCreated(_logger, order.Id, order.CustomerId);

```text

See [skill:dotnet-csharp-source-generators] for source generator mechanics and authoring patterns.

---

## AOT-Safe DI Patterns

Dependency injection in AOT requires explicit service registration. Assembly scanning (`AddServicesFromAssembly`) and
open-generic resolution may require reflection that AOT cannot satisfy.

### Explicit Registration (Preferred)

```csharp

var builder = WebApplication.CreateSlimBuilder(args);

// Explicit registrations -- AOT-safe
builder.Services.AddSingleton<IOrderRepository, PostgresOrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
builder.Services.AddSingleton(TimeProvider.System);

```text

### Avoid Assembly Scanning

```csharp

// BAD: Assembly scanning uses reflection -- breaks under AOT
builder.Services.Scan(scan => scan
    .FromAssemblyOf<OrderService>()
    .AddClasses(classes => classes.AssignableTo<IService>())
    .AsImplementedInterfaces()
    .WithScopedLifetime());

// GOOD: Explicit registrations grouped by concern
builder.Services.AddOrderServices();
builder.Services.AddInventoryServices();

// Extension method groups related registrations
public static class OrderServiceExtensions
{
    public static IServiceCollection AddOrderServices(
        this IServiceCollection services)
    {
        services.AddScoped<IOrderService, OrderService>();
        services.AddScoped<IOrderRepository, PostgresOrderRepository>();
        services.AddScoped<IOrderValidator, OrderValidator>();
        return services;
    }
}

```text

### Keyed Services (.NET 8+)

```csharp

// AOT-safe keyed service registration
builder.Services.AddKeyedSingleton<INotificationSender, EmailSender>("email");
builder.Services.AddKeyedSingleton<INotificationSender, SmsSender>("sms");

// Resolve by key
app.MapPost("/notify", ([FromKeyedServices("email")] INotificationSender sender) =>
    sender.SendAsync("Hello"));

```text

See [skill:dotnet-csharp-dependency-injection] for full DI patterns.

---

## Serialization Choices for AOT

### Decision Matrix

| Serializer                    | AOT-Safe | Setup Required                              | Best For                     |
| ----------------------------- | -------- | ------------------------------------------- | ---------------------------- |
| System.Text.Json + source gen | Yes      | `[JsonSerializable]` context                | APIs, config, JSON interop   |
| Protobuf (Google.Protobuf)    | Yes      | `.proto` schema files                       | gRPC, service-to-service     |
| MessagePack + source gen      | Yes      | `[MessagePackObject]` + source gen resolver | Caching, real-time           |
| Newtonsoft.Json               | **No**   | N/A                                         | **Do not use for AOT**       |
| STJ without source gen        | **No**   | N/A                                         | **Falls back to reflection** |

### STJ Source Gen Setup

```csharp

// Define serializable types
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal partial class AppJsonContext : JsonSerializerContext { }

// Register in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0,
        AppJsonContext.Default);
});

```json

See [skill:dotnet-serialization] for comprehensive serialization patterns.

---

## Factory Patterns Replacing Activator.CreateInstance

`Activator.CreateInstance` uses runtime reflection to create instances and is incompatible with AOT. Replace with
factory patterns that use explicit construction.

### Simple Factory

```csharp

// BAD: Reflection-based creation -- breaks under AOT
public T CreateHandler<T>() where T : class
    => (T)Activator.CreateInstance(typeof(T))!;

// GOOD: Factory with explicit registration
public class HandlerFactory
{
    private readonly Dictionary<Type, Func<IHandler>> _factories = new();

    public void Register<T>(Func<T> factory) where T : IHandler
        => _factories[typeof(T)] = () => factory();

    public IHandler Create<T>() where T : IHandler
        => _factories[typeof(T)]();
}

// Registration
var factory = new HandlerFactory();
factory.Register<OrderHandler>(() => new OrderHandler(repository, logger));
factory.Register<PaymentHandler>(() => new PaymentHandler(gateway));

```text

### Strategy Pattern via DI

```csharp

// BAD: Dynamic type resolution
public IPaymentProcessor GetProcessor(string type)
{
    var processorType = Type.GetType($"MyApp.Payments.{type}Processor");
    return (IPaymentProcessor)Activator.CreateInstance(processorType!)!;
}

// GOOD: Keyed services (.NET 8+)
builder.Services.AddKeyedScoped<IPaymentProcessor, CreditCardProcessor>("CreditCard");
builder.Services.AddKeyedScoped<IPaymentProcessor, BankTransferProcessor>("BankTransfer");
builder.Services.AddKeyedScoped<IPaymentProcessor, WalletProcessor>("Wallet");

// Resolve at runtime without reflection
app.MapPost("/pay", (
    [FromQuery] string type,
    IServiceProvider sp) =>
{
    var processor = sp.GetRequiredKeyedService<IPaymentProcessor>(type);
    return processor.ProcessAsync();
});

```text

### Enum-Based Factory

```csharp

// For a fixed set of types, use a switch expression
public static IExporter CreateExporter(ExportFormat format) => format switch
{
    ExportFormat.Csv => new CsvExporter(),
    ExportFormat.Json => new JsonExporter(),
    ExportFormat.Pdf => new PdfExporter(),
    _ => throw new ArgumentOutOfRangeException(nameof(format))
};

```json

---

## Library Compatibility Assessment

### Assessment Checklist

Before adopting a NuGet package in an AOT project:

1. **Check for `IsAotCompatible` in the package source** -- packages that set this are validated against AOT analyzers
2. **Check for `[RequiresDynamicCode]` / `[RequiresUnreferencedCode]` annotations** -- these indicate AOT-incompatible
   APIs
3. **Run AOT analyzers against your usage** -- `dotnet build /p:EnableAotAnalyzer=true`
4. **Check the package's GitHub issues for AOT/trimming reports** -- search for "Native AOT", "trimming", "IL2026",
   "IL3050"
5. **Look for source-generated alternatives** -- many reflection-based libraries now have source-gen companions

### Common Library Status

| Library               | AOT Status                       | AOT-Safe Alternative            |
| --------------------- | -------------------------------- | ------------------------------- |
| AutoMapper            | Breaks                           | Mapperly                        |
| MediatR               | Partial (explicit registration)  | Direct method calls or factory  |
| FluentValidation      | Partial                          | Manual validation or source gen |
| Dapper                | Compatible (.NET 8+ AOT support) | --                              |
| Entity Framework Core | Partial (precompiled queries)    | Dapper for AOT-heavy paths      |
| Refit                 | Compatible (7+ with source gen)  | --                              |
| Polly                 | Compatible (v8+)                 | --                              |
| Serilog               | Partial                          | `[LoggerMessage]` source gen    |
| Hangfire              | Breaks                           | Custom `IHostedService`         |

### Testing Compatibility

```bash

# Build with all analyzers enabled
dotnet build /p:EnableAotAnalyzer=true /p:EnableTrimAnalyzer=true /p:TrimmerSingleWarn=false

# Warnings indicate AOT-incompatible usage
# IL3050 = RequiresDynamicCode (definitely breaks)
# IL2026 = RequiresUnreferencedCode (may break)

```text

---

## AOT Application Architecture Template

```text

src/
  MyApp/
    Program.cs                   # CreateSlimBuilder, explicit DI
    MyApp.csproj                 # PublishAot=true, EnableAotAnalyzer=true
    JsonContext.cs               # [JsonSerializable] for all API types
    Endpoints/
      OrderEndpoints.cs          # Minimal API route groups
      ProductEndpoints.cs
    Services/
      OrderService.cs            # Business logic (no reflection)
      IOrderService.cs
    Repositories/
      OrderRepository.cs         # Data access (Dapper or EF precompiled)
    Extensions/
      ServiceCollectionExtensions.cs  # Grouped DI registrations

```csharp

---

## Agent Gotchas

1. **Do not use `Activator.CreateInstance` in AOT projects.** It requires runtime reflection that is not available. Use
   factory patterns, DI keyed services, or switch expressions instead.
2. **Do not use assembly scanning for DI registration** (`Scan`, `RegisterAssemblyTypes`, `FromAssemblyOf`). These use
   reflection to discover types at runtime. Register services explicitly.
3. **Do not use `System.Text.Json` without a `[JsonSerializable]` context in AOT.** Without a source-generated context,
   STJ falls back to reflection and fails at runtime.
4. **Do not assume a library is AOT-compatible without testing.** Run `dotnet build /p:EnableAotAnalyzer=true` and check
   for IL3050/IL2026 warnings against your specific usage.
5. **Do not use `Type.GetType()` or `Assembly.GetTypes()` for runtime discovery.** These rely on metadata that may be
   trimmed. Use compile-time known types.

---

## References

- [Native AOT deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
- [Source generation in .NET](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)
- [System.Text.Json source generation](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation)
- [LoggerMessage source generation](https://learn.microsoft.com/en-us/dotnet/core/extensions/logger-message-generator)
- [Mapperly object mapper](https://mapperly.riok.app/)
- [Prepare .NET libraries for trimming](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/prepare-libraries-for-trimming)
Weekly Installs
1
First Seen
11 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1