var logger = loggerFactory.CreateLogger<OrderService>();
logger.LogInformation("Order {OrderId} created for {Customer}", order.Id, order.CustomerId);
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);
}
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);
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
builder.Services.Scan(scan => scan
.FromAssemblyOf<OrderService>()
.AddClasses(classes => classes.AssignableTo<IService>())
.AsImplementedInterfaces()
.WithScopedLifetime());
builder.Services.AddOrderServices();
builder.Services.AddInventoryServices();
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
builder.Services.AddKeyedSingleton<INotificationSender, EmailSender>("email");
builder.Services.AddKeyedSingleton<INotificationSender, SmsSender>("sms");
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
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal partial class AppJsonContext : JsonSerializerContext { }
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
public T CreateHandler<T>() where T : class
=> (T)Activator.CreateInstance(typeof(T))!;
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)]();
}
var factory = new HandlerFactory();
factory.Register<OrderHandler>(() => new OrderHandler(repository, logger));
factory.Register<PaymentHandler>(() => new PaymentHandler(gateway));
```text
### Strategy Pattern via DI
```csharp
public IPaymentProcessor GetProcessor(string type)
{
var processorType = Type.GetType($"MyApp.Payments.{type}Processor");
return (IPaymentProcessor)Activator.CreateInstance(processorType!)!;
}
builder.Services.AddKeyedScoped<IPaymentProcessor, CreditCardProcessor>("CreditCard");
builder.Services.AddKeyedScoped<IPaymentProcessor, BankTransferProcessor>("BankTransfer");
builder.Services.AddKeyedScoped<IPaymentProcessor, WalletProcessor>("Wallet");
app.MapPost("/pay", (
[FromQuery] string type,
IServiceProvider sp) =>
{
var processor = sp.GetRequiredKeyedService<IPaymentProcessor>(type);
return processor.ProcessAsync();
});
```text
### Enum-Based Factory
```csharp
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.
---
## Code Navigation (Serena MCP)
**Primary approach:** Use Serena symbol operations for efficient code navigation:
1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications
**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## 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)