16 KiB
AOT Readiness Implementation Plan
Overview
Make the JdeScopingTool .NET 10 solution ready for Native AOT compilation by addressing three critical areas:
- JSON Source Generation - Replace reflection-based System.Text.Json with source generators
- Dapper AOT - Enable compile-time SQL mapping generation
- Configuration Binding - Replace reflection-based options binding and validation
Current AOT Score: ~25/100 (1 of 14 projects ready) Target AOT Score: 90/100 (all projects AOT-compatible with known library exceptions)
Current State Summary
Projects by AOT Readiness
| Status | Project | Key Blockers |
|---|---|---|
| ✅ Ready | JdeScoping.Domain |
None - pure domain models |
| ❌ Needs Work | JdeScoping.DataAccess |
Dapper, SqlKata, Oracle driver |
| ❌ Needs Work | JdeScoping.DataSync |
Dapper + STJ without source gen |
| ❌ Needs Work | JdeScoping.Core |
STJ + Configuration.Binder |
| ❌ Needs Work | JdeScoping.Infrastructure |
STJ + SecureStore + LDAP |
| ❌ Needs Work | JdeScoping.Api |
STJ + Swashbuckle + config binding |
| ❌ Needs Work | JdeScoping.Host |
Depends on API/DataSync |
| ❌ Needs Work | JdeScoping.Client |
STJ + Radzen + SignalR |
| ❌ Needs Work | JdeScoping.ExcelIO |
ClosedXML |
| ❌ Needs Work | JdeScoping.Database |
dbup-sqlserver |
| ❌ Needs Work | ConfigManager.* | STJ + System.CommandLine + Avalonia |
No Existing AOT Configuration
- No
PublishAotproperty - No
IsAotCompatiblemarkers - No
TrimModesettings - No
[DynamicallyAccessedMembers]attributes - No
[RequiresUnreferencedCode]attributes
Phase 1: Solution-Wide AOT Configuration
1.1 Create Directory.Build.props
File: NEW/src/Directory.Build.props (new)
<Project>
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<IsAotCompatible>true</IsAotCompatible>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
</Project>
1.2 Update Host Project for AOT Publishing
File: NEW/src/JdeScoping.Host/JdeScoping.Host.csproj
Add:
<PropertyGroup>
<PublishAot Condition="'$(PublishAot)' == ''">false</PublishAot>
<PublishTrimmed Condition="'$(PublishTrimmed)' == ''">true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
Phase 2: JSON Source Generation (~45 types)
2.1 Create JsonSerializerContext Classes
| Project | Context File | Types Count |
|---|---|---|
| JdeScoping.Core | Json/CoreJsonContext.cs |
~30 (ViewModels, Auth, Search) |
| JdeScoping.DataSync | Json/DataSyncJsonContext.cs |
~6 (ETL pipeline configs) |
| JdeScoping.Api | Json/ApiJsonContext.cs |
~10 (FileUploadResult, HealthCheck) |
| JdeScoping.Client | Json/ClientJsonContext.cs |
~15 (Client-side types) |
| ConfigManager.Core | Json/ConfigManagerJsonContext.cs |
~13 (ConfigModel sections) |
2.2 Types Requiring [JsonSerializable] Attributes
Core Authentication & Identity Types
UserInfoDto-JdeScoping.Core/Models/Auth/UserInfoDto.csLoginResultModel-JdeScoping.Core/Models/Auth/LoginResultModel.csLoginModel-JdeScoping.Core/Models/Auth/LoginModel.cs
Search & Criteria Types
SearchCriteria-JdeScoping.Core/Models/Search/SearchCriteria.csSearch-JdeScoping.Core/Models/Search/Search.csSearchViewModel-JdeScoping.Core/ViewModels/SearchViewModel.csSearchUpdateViewModel-JdeScoping.Core/ViewModels/SearchUpdateViewModel.csStatusUpdateViewModel-JdeScoping.Core/ViewModels/StatusUpdateViewModel.cs
File Upload & Result Types
FileUploadResult<T>-JdeScoping.Client/Models/FileUploadResult.csFileUploadResult<T>-JdeScoping.Api/Models/FileUploadResult.cs
Data Sync & Manual Sync Types
ManualSyncRequestViewModel-JdeScoping.Api/Contracts/ManualSync/ManualSyncRequestViewModel.csCreateManualSyncRequestDto-JdeScoping.Core/ViewModels/CreateManualSyncRequestDto.csPipelineInfoViewModel-JdeScoping.Api/Contracts/ManualSync/PipelineInfoViewModel.cs
ETL Pipeline Configuration Types
EtlPipelineConfig-JdeScoping.DataSync/Configuration/EtlPipelineConfig.csSourceElement-JdeScoping.DataSync/Configuration/SourceElement.csDestinationElement-JdeScoping.DataSync/Configuration/DestinationElement.csTransformElement-JdeScoping.DataSync/Configuration/TransformElement.csScriptElement-JdeScoping.DataSync/Configuration/ScriptElement.csParameterElement-JdeScoping.DataSync/Configuration/ParameterElement.cs
Configuration Model Types
ConfigModel-Utils/JdeScoping.ConfigManager.Core/Models/ConfigModel.csConnectionStringsSection-Utils/JdeScoping.ConfigManager.Core/Models/ConnectionStringsSection.csConnectionStringEntry-Utils/JdeScoping.ConfigManager.Core/Models/ConnectionStringEntry.cs
Data ViewModels
WorkOrderViewModel,ItemViewModel,WorkCenterViewModel,ProfitCenterViewModelPartOperationViewModel,JdeUserViewModel,LotViewModel,ComponentLotViewModel,OperatorViewModel
2.3 Key Files to Create
NEW/src/JdeScoping.Core/Json/CoreJsonContext.cs - Central context with:
- All ViewModels (SearchViewModel, WorkOrderViewModel, ItemViewModel, etc.)
- Auth models (UserInfoDto, LoginResultModel, LoginModel)
- SearchCriteria, ApiError, ValidationError
NEW/src/JdeScoping.DataSync/Json/DataSyncJsonContext.cs:
- EtlPipelineConfig, SourceElement, DestinationElement, TransformElement
NEW/src/JdeScoping.Api/Json/ApiJsonContext.cs:
- FileUploadResult instantiations for each ViewModel type
- HealthCheckResponse (replace anonymous type)
2.4 Registration Updates
NEW/src/JdeScoping.Api/DependencyInjection.cs (line ~80):
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, CoreJsonContext.Default);
options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonContext.Default);
});
NEW/src/JdeScoping.Client/Services/ApiClientBase.cs (line ~16):
private static readonly JsonSerializerOptions JsonOptions = new()
{
TypeInfoResolver = JsonTypeInfoResolver.Combine(
ClientJsonContext.Default,
CoreJsonContext.Default)
};
2.5 Fix Anonymous Type in Health Check
NEW/src/JdeScoping.Api/DependencyInjection.cs - Replace anonymous object with typed HealthCheckResponse class.
Phase 3: Dapper AOT (15+ model types)
3.1 Dapper Usage Locations
| File | Dapper Methods | Model Types |
|---|---|---|
LotFinderRepository.SearchManagement.cs |
QueryAsync, QueryFirstOrDefaultAsync | Search |
LotFinderRepository.Lookups.cs |
QueryAsync | Item, Lot, WorkOrder, WorkCenter, ProfitCenter, JdeUser |
LotFinderRepository.DataSync.cs |
QueryAsync | DataUpdate |
SearchProcessor.cs |
QueryAsync, QuerySingleOrDefaultAsync | SearchResult, MisSearchResult, MisNonMatchSearchResult |
ManualSyncRequestService.cs |
QueryAsync, QuerySingleAsync, ExecuteAsync | ManualSyncRequest |
WorkOrderTraversalService.cs |
QuerySingleAsync | (int, int) tuple |
DataUpdateRepository.cs |
QueryAsync, ExecuteScalarAsync, ExecuteAsync | DataUpdate, (string, int, DateTime?, int) tuple |
SearchRepository.cs |
QueryFirstOrDefaultAsync, ExecuteAsync | Search |
3.2 Add Dapper.AOT Package
Files: JdeScoping.DataAccess.csproj, JdeScoping.DataSync.csproj
<PackageReference Include="Dapper.AOT" Version="1.0.48" />
<PropertyGroup>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Dapper.AOT</InterceptorsPreviewNamespaces>
</PropertyGroup>
3.3 Enable AOT for Assemblies
NEW/src/JdeScoping.DataAccess/DapperAotConfiguration.cs (new):
[assembly: DapperAot]
3.4 Fix Private Field Mapping Pattern
Change 6 models from private to internal for Dapper.AOT compatibility:
| Model | File |
|---|---|
| Item | Core/Models/Inventory/Item.cs |
| Lot | Core/Models/Inventory/Lot.cs |
| WorkOrder | Core/Models/WorkOrders/WorkOrder.cs |
| WorkCenter | Core/Models/Organization/WorkCenter.cs |
| ProfitCenter | Core/Models/Organization/ProfitCenter.cs |
| JdeUser | Core/Models/Organization/JdeUser.cs |
Change:
private int LastUpdateDate { get; set; } // Before
[Column("LastUpdateDate")]
internal int LastUpdateDate { get; set; } // After
3.5 Replace Tuple Queries with Named Types
Create explicit result types:
NEW/src/JdeScoping.DataAccess/Models/TraversalResult.cs (new):
public sealed class TraversalResult
{
public int IterationsCompleted { get; init; }
public int TotalWorkOrders { get; init; }
}
NEW/src/JdeScoping.DataSync/Models/TableSyncStatusRow.cs (new):
public sealed class TableSyncStatusRow
{
public string TableName { get; init; } = string.Empty;
public int UpdateType { get; init; }
public DateTime? LastSuccessfulSync { get; init; }
public int RecentFailures { get; init; }
}
Phase 4: Configuration Binding
4.1 Options Classes Inventory
| Project | Options Class | Has DataAnnotations | Uses ValidateDataAnnotations |
|---|---|---|---|
| JdeScoping.Core | SecureStoreOptions | No | No |
| JdeScoping.Core | RsaKeyOptions | No | No |
| JdeScoping.Infrastructure | LdapOptions | Yes (1 Range) | No |
| JdeScoping.Api | AuthOptions | No | No |
| JdeScoping.DataAccess | DataAccessOptions | No | No |
| JdeScoping.DataAccess | SearchProcessingOptions | No | No |
| JdeScoping.DataAccess | SearchProcessingConfiguration | No | No |
| JdeScoping.DataSync | DataSyncOptions | Yes (6 Range) | Yes |
| JdeScoping.DataSync | WorkProcessorOptions | Yes (3 Range) | Yes |
| JdeScoping.ExcelIO | ExcelExportOptions | No | No |
| JdeScoping.DataSync.Dev | DevPipelineOptions | No | No |
4.2 Create AOT-Compatible Validators
Replace ValidateDataAnnotations() with IValidateOptions<T>:
NEW/src/JdeScoping.DataSync/Validation/DataSyncOptionsValidator.cs (new)
NEW/src/JdeScoping.DataSync/Validation/WorkProcessorOptionsValidator.cs (new)
4.3 Update DependencyInjection Files
NEW/src/JdeScoping.DataSync/DependencyInjection.cs (lines 27-36):
// Before
.ValidateDataAnnotations()
// After
services.AddSingleton<IValidateOptions<DataSyncOptions>, DataSyncOptionsValidator>();
NEW/src/JdeScoping.Infrastructure/DependencyInjection.cs (lines 33-35):
Replace GetSection().Get<T>() with GetSection().GetValue<T>().
NEW/src/JdeScoping.Api/DependencyInjection.cs (lines 33-35):
Replace GetSection().Get<T>() with explicit property binding.
4.4 Remove Unused Package
NEW/src/JdeScoping.DataSync/JdeScoping.DataSync.csproj:
Remove Microsoft.Extensions.Options.DataAnnotations reference.
Phase 5: Third-Party Library Handling
5.1 Package Risk Assessment
| Risk Level | Packages |
|---|---|
| 🔴 High | Dapper (mitigated by Dapper.AOT), SqlKata, Swashbuckle, ClosedXML, dbup-sqlserver |
| 🟠 Medium | protobuf-net-data, System.CommandLine, SecureStore |
| 🟡 Low | Microsoft.Data.SqlClient, Oracle driver, LDAP, Serilog |
| 🟢 Safe | Cronos, OneOf (has source generator), ZstdSharp |
5.2 Create Trimmer Roots
NEW/src/JdeScoping.Host/TrimmerRoots.xml (new):
<linker>
<assembly fullname="Dapper">
<type fullname="Dapper.*" preserve="all" />
</assembly>
<assembly fullname="Oracle.ManagedDataAccess">
<type fullname="Oracle.ManagedDataAccess.*" preserve="all" />
</assembly>
<assembly fullname="ClosedXML">
<type fullname="ClosedXML.*" preserve="all" />
</assembly>
<assembly fullname="SqlKata">
<type fullname="SqlKata.*" preserve="all" />
</assembly>
</linker>
5.3 Known Limitations
These packages have partial/no AOT support - preserved via trimmer roots:
- ClosedXML (Excel generation)
- Oracle.ManagedDataAccess.Core
- dbup-sqlserver (migrations - dev only)
- Swashbuckle (Swagger - dev only)
Implementation Sequence
| Step | Task | Files |
|---|---|---|
| 1 | Create Directory.Build.props | 1 new |
| 2 | Create CoreJsonContext | 1 new |
| 3 | Create DataSyncJsonContext | 1 new |
| 4 | Create ApiJsonContext + HealthCheckResponse | 1 new |
| 5 | Create ClientJsonContext | 1 new |
| 6 | Create ConfigManagerJsonContext | 1 new |
| 7 | Update Api DependencyInjection (JSON registration) | 1 edit |
| 8 | Update ApiClientBase (JSON options) | 1 edit |
| 9 | Update PipelineRegistry (JSON options) | 1 edit |
| 10 | Add Dapper.AOT packages | 2 edits |
| 11 | Create DapperAotConfiguration | 1 new |
| 12 | Update 6 models (private→internal) | 6 edits |
| 13 | Create TraversalResult, TableSyncStatusRow | 2 new |
| 14 | Update tuple queries to use named types | 2 edits |
| 15 | Create DataSyncOptionsValidator | 1 new |
| 16 | Create WorkProcessorOptionsValidator | 1 new |
| 17 | Update DataSync DependencyInjection | 1 edit |
| 18 | Update Infrastructure DependencyInjection | 1 edit |
| 19 | Update Api DependencyInjection (config) | 1 edit |
| 20 | Update Host csproj (AOT settings) | 1 edit |
| 21 | Create TrimmerRoots.xml | 1 new |
| 22 | Remove DataAnnotations package | 1 edit |
Total: 14 new files, 18 edits
Verification
Build Verification
dotnet build NEW/JdeScoping.slnx
Should complete with no new errors.
AOT Analysis
dotnet publish NEW/src/JdeScoping.Host -c Release -p:PublishAot=true --no-build
Check for AOT compatibility warnings.
Trim Analysis
dotnet publish NEW/src/JdeScoping.Host -c Release -p:PublishTrimmed=true
Review trim warnings and verify TrimmerRoots coverage.
Test Suite
dotnet test NEW/JdeScoping.slnx
All existing tests should pass.
E2E Verification
Run Playwright tests to verify JSON serialization works end-to-end.
Critical Files Summary
New Files (14):
NEW/src/Directory.Build.propsNEW/src/JdeScoping.Core/Json/CoreJsonContext.csNEW/src/JdeScoping.DataSync/Json/DataSyncJsonContext.csNEW/src/JdeScoping.Api/Json/ApiJsonContext.csNEW/src/JdeScoping.Client/Json/ClientJsonContext.csNEW/src/Utils/JdeScoping.ConfigManager.Core/Json/ConfigManagerJsonContext.csNEW/src/JdeScoping.DataAccess/DapperAotConfiguration.csNEW/src/JdeScoping.DataAccess/Models/TraversalResult.csNEW/src/JdeScoping.DataSync/Models/TableSyncStatusRow.csNEW/src/JdeScoping.DataSync/Validation/DataSyncOptionsValidator.csNEW/src/JdeScoping.DataSync/Validation/WorkProcessorOptionsValidator.csNEW/src/JdeScoping.Host/TrimmerRoots.xml
Key Edits:
NEW/src/JdeScoping.Api/DependencyInjection.cs- JSON + configNEW/src/JdeScoping.Client/Services/ApiClientBase.cs- JSON optionsNEW/src/JdeScoping.DataSync/DependencyInjection.cs- ValidatorsNEW/src/JdeScoping.Infrastructure/DependencyInjection.cs- Config bindingNEW/src/JdeScoping.Host/JdeScoping.Host.csproj- AOT settings- 6 model files for private→internal field change