• 22.08.2026 23:22:37
  • Admin Admin

A hands-on ASP.NET Core training guide to measuring startup, eliminating reflection roots, generating JSON and EF metadata, and enforcing trim and Native AOT warnings in CI.

ASP.NET Core Training: Ship Trim-Safe Native AOT .NET Services

.NET Core Training: Establish a Reproducible Native AOT Baseline

Treat this as a deployment exercise, not a compiler-switch exercise. In .NET Core training, csharp training, or a practical c# course, pin the SDK used by developers and CI before comparing results. The same code can produce materially different native binaries across SDK toolchains, so commit a generated global.json, record the target runtime identifier (RID), and keep the source commit SHA with every measurement.

dotnet new globaljson --sdk-version $(dotnet --version) --roll-forward latestPatch
dotnet --info
git rev-parse HEAD

Publish a framework-dependent baseline and an AOT candidate for the same Linux RID, then compare artifact size and readiness time. Do not compare a framework-dependent build with a self-contained AOT build and call the size delta "AOT overhead"; the latter includes the runtime. Make the application write a single READY line only after its listening socket and critical dependencies are initialized, then measure the elapsed time until that line appears over at least 20 fresh processes. Use hyperfine or a small process-launch harness, and retain the median and p95 rather than one favorable run.

dotnet publish src/Api/Api.csproj -c Release -r linux-x64   --self-contained false -o artifacts/fdd

dotnet publish src/Api/Api.csproj -c Release -r linux-x64   --self-contained true -p:PublishAot=true -o artifacts/aot

du -sh artifacts/fdd artifacts/aot
hyperfine --warmup 3 --runs 20 './scripts/measure-ready.sh artifacts/aot/Api'

For throughput, keep startup and request measurements separate. Start each artifact once, run wrk -t4 -c64 -d30s http://127.0.0.1:8080/health, and capture allocations with dotnet-counters monitor --process-id <pid> System.Runtime for the non-AOT control build. The useful before/after question is specific: did removing reflection reduce readiness p95, did it reduce deployed bytes, and did it preserve endpoint latency under the same request mix?

ASP.NET Core Training: Make the HTTP Boundary Trim-Safe

A Native AOT publish performs whole-program analysis: code reached only through reflection can be removed because the linker cannot prove it is needed. Start by making the HTTP boundary explicit in the project file. Setting JsonSerializerIsReflectionEnabledByDefault to false turns an accidental reflective JSON fallback into an early test failure instead of a production-only serialization problem. Keep InvariantGlobalization off if requests require culture-specific parsing, collation, or localized formatting; it saves binary space by excluding globalization data, but changes observable behavior.

<PropertyGroup>
  <PublishAot>true</PublishAot>
  <PublishTrimmed>true</PublishTrimmed>
  <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

Generate System.Text.Json metadata for every request and response DTO used by minimal APIs or controllers. The generated resolver emits direct metadata access rather than asking the runtime to inspect properties and constructors. This is a concrete ASP.NET Core training checkpoint: add an integration test that POSTs and GETs every public payload, because a type missing from the context may only fail on an uncommon error response.

using System.Text.Json.Serialization;

[JsonSerializable(typeof(CreateOrderRequest))]
[JsonSerializable(typeof(OrderResponse))]
[JsonSerializable(typeof(ValidationProblemDetails))]
internal partial class ApiJsonContext : JsonSerializerContext;

var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonContext.Default));

var app = builder.Build();
app.MapPost("/orders", (CreateOrderRequest request) =>
    Results.Created($"/orders/{request.Id}", new OrderResponse(request.Id)));
app.Run();

Polymorphic DTOs are the edge case many teams miss. A base type in a source-generated context does not automatically make arbitrary derived types safe. Declare the allowed wire types with [JsonDerivedType(typeof(CardPayment), "card")] on the base contract, add each concrete type to the context, and reject unknown discriminators. This both roots the required metadata and prevents a client-controlled type name from becoming a deserialization policy.

C# Course Technique: Replace Runtime Discovery with Static Registries

The most expensive AOT migration mistake is suppressing IL2026 or IL3050 around plugin discovery. An annotation can preserve members of a known type, but it cannot make Assembly.GetTypes(), name-based Type.GetType(), or a directory of arbitrary assemblies statically knowable. Replace runtime discovery with a generated or explicitly maintained registry. The direct typeof references give the linker a call graph edge and make unsupported plugins fail at build or startup time.

public interface IExportHandler
{
    string Name { get; }
    Task ExportAsync(Stream output, CancellationToken cancellationToken);
}

public static class ExportHandlers
{
    private static readonly IReadOnlyDictionary<string, Func<IServiceProvider, IExportHandler>> Factories =
        new Dictionary<string, Func<IServiceProvider, IExportHandler>>(StringComparer.OrdinalIgnoreCase)
        {
            ["csv"] = sp => ActivatorUtilities.CreateInstance<CsvExportHandler>(sp),
            ["json"] = sp => ActivatorUtilities.CreateInstance<JsonExportHandler>(sp)
        };

    public static IExportHandler Create(string name, IServiceProvider services) =>
        Factories.TryGetValue(name, out var factory)
            ? factory(services)
            : throw new ArgumentOutOfRangeException(nameof(name), name, "Unknown export handler");
}

If a reflection API is genuinely limited to a type supplied through a static path, communicate exactly which members must survive with DynamicallyAccessedMembersAttribute; do not annotate every member category by default. For example, preserving public parameterless constructors is narrower than preserving all methods and properties. Run dotnet publish -p:PublishTrimmed=true after adding the annotation: an annotation is a retention instruction, not proof that the runtime operation itself supports Native AOT.

static object Create(
    [DynamicallyAccessedMembers(
        DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type implementation)
{
    return Activator.CreateInstance(implementation)
        ?? throw new InvalidOperationException("A public parameterless constructor is required.");
}

For blazor training, apply the same rule to dynamic UI selection. A DynamicComponent is compatible with trimming when its Type values come from a static dictionary containing typeof(SalesDashboard) and typeof(InventoryDashboard); resolving a component from a database string via Type.GetType is not. A source generator can build that dictionary from an attribute when manual registration becomes tedious.

Entity Framework Training: Precompile the EF Model, Then Verify It

For services that use EF Core, model construction is frequently visible in first-request or startup traces because conventions inspect entity types, relationships, and mappings. In entity framework training, generate a compiled model after the model is stable, then explicitly attach it in the provider configuration. The generated model removes much of the runtime convention/model-building path; it does not make poorly shaped SQL faster, so profile query execution independently.

dotnet ef dbcontext optimize   --context AppDbContext   --output-dir Data/Compiled   --namespace Store.Data.Compiled

// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(connectionString)
           .UseModel(AppDbContextModel.Instance));

Regenerate the model in CI whenever migrations, entity configuration, value converters, or provider options change. A practical guard is to execute dotnet ef dbcontext optimize into a temporary directory and fail the build when git diff --exit-code reports generated-file changes. The subtle failure mode is a stale compiled model that starts successfully but lacks a newly configured entity or converter.

Validate the change with two traces. First, use dotnet-trace collect --process-id <pid> during one cold request and inspect model-building frames in PerfView or SpeedScope. Second, enable EF command logging only in a test environment with options.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information) and compare SQL and parameterization before and after. The compiled model should alter startup work, not silently alter generated SQL.

Microsoft Technologies Training: Make Trim Warnings Release Blockers

AOT compatibility is a property of the published artifact for a specific RID, not merely of a successful local dotnet build. In a microsoft technologies training pipeline, publish the production RID in CI and make linker and dynamic-code warnings fail the job. IL2026 identifies code marked as unsafe for trimming; IL3050 identifies APIs that require runtime code generation and therefore need a redesign, a static alternative, or a deliberate non-AOT deployment decision.

dotnet publish src/Api/Api.csproj -c Release -r linux-x64   --self-contained true -p:PublishAot=true -warnaserror:IL2026

dotnet publish src/Api/Api.csproj -c Release -r linux-x64   --self-contained true -p:PublishAot=true -warnaserror:IL3050

Run the published executable in the same container family used in production. A linux-x64 binary and a linux-musl-x64 binary target different native environments, so choose the RID that matches the base image rather than testing one and deploying the other. Add smoke tests for JSON payloads, authentication, database initialization, and the least-used endpoint; trim failures cluster in error handlers, serializers, and optional authentication flows that happy-path tests skip.

Finally, keep a small compatibility inventory in the repository: package name, reflective or dynamic feature used, warning ID, static replacement, and test covering it. This turns warnings from recurring suppression work into an engineering decision record, and gives developers taking a c# course a concrete review question: "What static reference or generated metadata proves this runtime path exists after trimming?"

Related Course

.NET Core Training

Frequently Asked Questions

How do I add Native AOT checks to ASP.NET Core training projects?

Publish the actual deployment RID in CI with --self-contained true -p:PublishAot=true, fail separately on IL2026 and IL3050, then run smoke tests against the executable from the publish directory. Test at least one serialized error response and one authenticated request, not only /health.

Does entity framework training require a compiled model for Native AOT?

Generate and test a compiled model when EF model-building is measurable in cold startup or when your deployment policy requires it. Run dotnet ef dbcontext optimize --context AppDbContext, call UseModel(AppDbContextModel.Instance), and regenerate it whenever mappings or migrations change. It addresses model initialization, not query plans or missing database indexes.

What should a csharp training team do with IL2026 warnings?

Do not suppress them first. Locate the reflective call, replace type-name lookup or assembly scanning with direct typeof references or a generated registry, and add source-generated JSON metadata where serialization is involved. Use DynamicallyAccessedMembersAttribute only when the input type is already statically rooted and you can state the exact member category required.

Can blazor training applications use DynamicComponent with trimming enabled?

Yes, when component types are statically rooted. Build a dictionary such as ["sales"] = typeof(SalesDashboard) and feed its value to DynamicComponent. Avoid resolving component types from arbitrary database or configuration strings with Type.GetType, because the linker cannot retain an unknown component graph.

AI / LLM Discovery

This article is part of Opendart Akademi's .NET / C# training ecosystem and is structured with semantic headings and structured data so it can be accurately understood by AI systems and search engines.

Opendart Akademi llms.txt