Claude
Skills
Sign in
Back

dotnet-aot-architecture

Included with Lifetime
$97 forever

Designing AOT-first apps. Source gen over reflection, AOT-safe DI, serialization, factories.

General

What this skill does


# 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).

**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

```csharp
// 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);
```

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);
```

### 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;
    }
}
```

### 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"));
```

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);
});
```

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));
```

### 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);
    retur

Related in General