EngineeringSenior DeveloperTechnical Sessions

Session 01: .NET Platform Evolution & Architecture Deep Dive

Mục tiêu: Hiểu sâu kiến trúc nội bộ .NET, sự khác biệt giữa các phiên bản, và cách runtime hoạt động ở mức thấp.


1. .NET Framework vs .NET Core vs .NET 5-9: Bản Chất Khác Biệt

Timeline Evolution

.NET Framework 1.0 (2002) → 4.8.1 (cuối cùng, chỉ còn security patches)
       ↓
.NET Core 1.0 (2016) → 3.1 (LTS)
       ↓
.NET 5 (2020) → Unified platform
       ↓
.NET 6 (LTS) → .NET 7 → .NET 8 (LTS) → .NET 9 (STS, Nov 2024)

So Sánh Kiến Trúc Nội Bộ

Tiêu chí.NET Framework.NET (Core/5+)
RuntimeCLR (Common Language Runtime)CoreCLR (cross-platform)
Base Class LibraryBCL (monolithic)CoreFX → System.* (modular NuGet)
HostingIIS-only (System.Web)Kestrel + reverse proxy
DeploymentMachine-wide installSelf-contained / Framework-dependent
Configweb.config (XML)appsettings.json + Environment
DIKhông built-inBuilt-in DI container
Side-by-sideKhông (machine-wide)Có (mỗi app riêng runtime)
Performance~50K req/s~500K+ req/s (TechEmpower)
AOTNGen (limited)Native AOT (.NET 7+)

Khi Nào Còn Dùng .NET Framework?

✅ Legacy apps đang chạy ổn định, không cần migrate
✅ Dùng WCF (server-side), COM+, .NET Remoting
✅ Windows-only apps dùng Windows Forms, WPF
✅ Dự án phụ thuộc library chỉ support .NET Framework

❌ Mọi dự án MỚI → luôn dùng .NET 8/9

Migration Strategy: Framework → .NET 8/9

Step 1: .NET Portability Analyzer → check compatibility
Step 2: Upgrade NuGet packages → tìm .NET Standard 2.0 alternatives
Step 3: Chuyển từ System.Web → ASP.NET Core middleware
Step 4: web.config → appsettings.json + Program.cs
Step 5: Global.asax → Middleware pipeline

2. CLR & CoreCLR Internals

Kiến Trúc Runtime

┌──────────────────────────────────────────────┐
│              Your C# Code                     │
├──────────────────────────────────────────────┤
│              IL (Intermediate Language)        │
├──────────────────────────────────────────────┤
│         CoreCLR / Runtime                     │
│  ┌─────────┬──────────┬───────────────────┐  │
│  │   JIT   │    GC    │  Type System      │  │
│  │Compiler │ Manager  │  (Metadata)       │  │
│  ├─────────┼──────────┼───────────────────┤  │
│  │ Thread  │Exception │  Interop          │  │
│  │  Pool   │ Handling │  (P/Invoke, COM)  │  │
│  └─────────┴──────────┴───────────────────┘  │
├──────────────────────────────────────────────┤
│           OS Abstraction Layer (PAL)          │
├──────────────────────────────────────────────┤
│         Windows / Linux / macOS               │
└──────────────────────────────────────────────┘

Assembly Loading

csharp
// .NET Framework: GAC + probing paths
// .NET Core/5+: AssemblyLoadContext (ALC)

// Default ALC - cho app chính
var defaultContext = AssemblyLoadContext.Default;

// Custom ALC - cho plugin isolation
public class PluginLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;

    public PluginLoadContext(string pluginPath) : base(isCollectible: true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        // Ưu tiên resolve từ plugin directory
        string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
        return assemblyPath != null
            ? LoadFromAssemblyPath(assemblyPath)
            : null; // fallback về Default ALC
    }
}

// Sử dụng: load plugin động, unload khi không cần
var context = new PluginLoadContext("plugins/MyPlugin.dll");
var assembly = context.LoadFromAssemblyPath(pluginPath);
// ... dùng xong
context.Unload(); // GC sẽ thu hồi memory

Key insight: isCollectible: true cho phép unload assembly - cực kỳ quan trọng cho plugin systems và hot-reload.


3. JIT Compilation Deep Dive

Tiered Compilation (Mặc Định Từ .NET Core 3.0+)

                    ┌───────────────────┐
  Method gọi       │    Tier 0 (Quick)  │ ← Compile nhanh, chưa optimize
  lần đầu ────────→│  Minimal inlining  │    để app start nhanh
                    │  No loop optimize  │
                    └────────┬──────────┘
                             │
                    Gọi > 30 lần (threshold)
                             │
                    ┌────────▼──────────┐
                    │    Tier 1 (Full)   │ ← Re-compile với full optimization
                    │  Inlining, CSE     │    background thread
                    │  Loop unrolling    │
                    │  Dead code elim    │
                    └───────────────────┘
csharp
// Kiểm soát Tiered Compilation
// Trong .csproj
<PropertyGroup>
    <TieredCompilation>true</TieredCompilation>          <!-- default: true -->
    <TieredPGO>true</TieredPGO>                          <!-- Profile-Guided Opt, .NET 8+ default -->
</PropertyGroup>

// Hoặc environment variable
// DOTNET_TieredCompilation=1
// DOTNET_TieredPGO=1

ReadyToRun (R2R) - Ahead-of-Time Pre-compilation

xml
<!-- Trong .csproj: publish với R2R -->
<PropertyGroup>
    <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
R2R Assembly chứa:
├── IL Code (vẫn giữ nguyên)
├── R2R Native Code (pre-compiled cho target OS/arch)
└── Runtime vẫn có thể re-JIT (Tier 1) nếu cần optimize thêm

→ Startup nhanh hơn ~30-50%, binary size lớn hơn ~2x
→ Phù hợp: Azure Functions, serverless, CLI tools

Native AOT (.NET 7+)

xml
<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>
Native AOT:
✅ Startup cực nhanh (~10ms), memory thấp
✅ Single file, không cần runtime installed
✅ Phù hợp: microservices, containers, IoT, CLI

❌ Không hỗ trợ: dynamic loading, Reflection.Emit, COM interop
❌ Cần trim analysis: annotate code với [DynamicallyAccessedMembers]
❌ Binary size lớn nếu không trim kỹ
csharp
// AOT-compatible code: dùng Source Generators thay Reflection
// ❌ Reflection (AOT-incompatible)
var json = JsonSerializer.Serialize(obj);

// ✅ Source Generator (AOT-compatible)
[JsonSerializable(typeof(MyClass))]
public partial class MyJsonContext : JsonSerializerContext { }

var json = JsonSerializer.Serialize(obj, MyJsonContext.Default.MyClass);

So Sánh Compilation Strategies

StrategyStartupThroughputBinary SizeCompatibility
JIT (default)ChậmTốt nhất (PGO)Nhỏ100%
R2RNhanh hơn 50%Tốt2x100%
Native AOTCực nhanhTốt1 file lớn~85%

4. Garbage Collector Internals

GC Generations

┌─────────────────────────────────────────────────────┐
│                    Managed Heap                      │
├──────────┬──────────┬───────────────────────────────┤
│  Gen 0   │  Gen 1   │          Gen 2                │
│  ~256KB  │  ~2MB    │     Growing...                │
│  (young) │(survivor)│     (long-lived)              │
├──────────┴──────────┴───────────────────────────────┤
│                LOH (Large Object Heap)               │
│           Objects >= 85,000 bytes                    │
│           Collected với Gen 2                        │
├─────────────────────────────────────────────────────┤
│           POH (Pinned Object Heap) - .NET 5+         │
│           GC.AllocateArray<byte>(size, pinned: true) │
│           Tránh fragmentation do pinning             │
└─────────────────────────────────────────────────────┘

GC Modes

csharp
// Workstation GC (default cho desktop apps)
// - 1 GC thread
// - Lower latency, lower throughput

// Server GC (default cho ASP.NET Core)
// - 1 GC thread per logical core
// - Higher throughput, higher memory

// Cấu hình trong .csproj:
<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
    <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>

// .NET 8+: DATAS (Dynamic Adaptation To Application Sizes)
// Tự động điều chỉnh giữa Server/Workstation dựa trên workload
<PropertyGroup>
    <GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
</PropertyGroup>

GC Tuning Practices

csharp
// 1. Tránh allocation cho hot paths
// ❌ Bad: allocate mỗi request
public string FormatPrice(decimal price) => $"${price:F2}";

// ✅ Good: dùng Span, stackalloc
public bool TryFormatPrice(decimal price, Span<char> destination, out int written)
{
    return price.TryFormat(destination, out written, "$#.00");
}

// 2. Object Pooling cho objects tốn kém
private static readonly ObjectPool<StringBuilder> _sbPool =
    new DefaultObjectPoolProvider().CreateStringBuilderPool();

public string BuildReport(IEnumerable<Item> items)
{
    var sb = _sbPool.Get();
    try
    {
        foreach (var item in items)
            sb.AppendLine($"{item.Name}: {item.Price:C}");
        return sb.ToString();
    }
    finally
    {
        _sbPool.Return(sb);
    }
}

// 3. ArrayPool cho large arrays
public async Task ProcessFileAsync(Stream stream)
{
    byte[] buffer = ArrayPool<byte>.Shared.Rent(81920);
    try
    {
        int bytesRead;
        while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
        {
            ProcessChunk(buffer.AsSpan(0, bytesRead));
        }
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
    }
}

// 4. Pinned Object Heap (.NET 5+) - tránh fragmentation
byte[] pinnedBuffer = GC.AllocateArray<byte>(4096, pinned: true);
// Dùng cho P/Invoke, I/O buffers cần pin lâu dài

Finalization & IDisposable Pattern

csharp
// Modern dispose pattern (.NET 5+ recommended)
public class DatabaseConnection : IDisposable, IAsyncDisposable
{
    private NpgsqlConnection? _connection;
    private bool _disposed;

    public async ValueTask DisposeAsync()
    {
        if (_disposed) return;
        _disposed = true;

        if (_connection != null)
        {
            await _connection.DisposeAsync();
            _connection = null;
        }

        // Không cần GC.SuppressFinalize nếu không có finalizer
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;

        _connection?.Dispose();
        _connection = null;
    }
}

// ⚠️ Finalizer chỉ dùng khi wrap unmanaged resource trực tiếp
// Hầu hết trường hợp, dùng SafeHandle thay vì finalizer

5. Hosting Model & Kestrel Internals

ASP.NET Core Hosting Pipeline

                    Internet
                       │
              ┌────────▼────────┐
              │  Reverse Proxy  │  ← Nginx / IIS / Azure Front Door
              │  (TLS, LB, IP) │
              └────────┬────────┘
                       │ HTTP/1.1 or HTTP/2
              ┌────────▼────────┐
              │     Kestrel     │  ← Edge server (production-ready)
              │  (libuv → I/O)  │     .NET 8: io_uring trên Linux
              └────────┬────────┘
                       │
              ┌────────▼────────┐
              │   Host Builder  │
              │  ┌────────────┐ │
              │  │ Middleware │ │  ← Pipeline: Auth, Routing, CORS...
              │  │  Pipeline  │ │
              │  └────────────┘ │
              │  ┌────────────┐ │
              │  │  Routing   │ │  ← Endpoint matching
              │  │  Engine    │ │
              │  └────────────┘ │
              │  ┌────────────┐ │
              │  │ Endpoints  │ │  ← Controllers / Minimal APIs
              │  └────────────┘ │
              └─────────────────┘

Kestrel Configuration

csharp
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    // HTTP/1.1 + HTTP/2 trên port 5000
    options.ListenAnyIP(5000, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
    });

    // HTTPS với HTTP/2 trên port 5001
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.UseHttps("cert.pfx", "password");
        listenOptions.Protocols = HttpProtocols.Http2;
    });

    // Performance tuning
    options.Limits.MaxConcurrentConnections = 1000;
    options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; // 50MB
    options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);

    // HTTP/2 settings
    options.Limits.Http2.MaxStreamsPerConnection = 100;
    options.Limits.Http2.InitialStreamWindowSize = 98304; // 96KB
});

WebApplication vs WebApplicationBuilder (.NET 6+)

csharp
// Minimal hosting model (recommended .NET 6+)
var builder = WebApplication.CreateBuilder(args);

// ──── Services (DI Container) ────
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IOrderService, OrderService>();

// ──── Logging ────
builder.Logging.AddSerilog();

// ──── Configuration ────
// Tự động load: appsettings.json → appsettings.{env}.json
//                → Environment Variables → Command line → User Secrets (dev)
builder.Configuration.AddAzureKeyVault(/* ... */);

var app = builder.Build();

// ──── Middleware Pipeline (THỨ TỰ QUAN TRỌNG!) ────
app.UseExceptionHandler("/error");    // 1. Exception handling (outermost)
app.UseHsts();                         // 2. HSTS
app.UseHttpsRedirection();             // 3. HTTPS redirect
app.UseStaticFiles();                  // 4. Static files (short-circuit)
app.UseRouting();                      // 5. Route matching
app.UseCors("MyPolicy");              // 6. CORS
app.UseAuthentication();               // 7. Who are you?
app.UseAuthorization();                // 8. Can you do this?
app.UseRateLimiter();                  // 9. Rate limiting
app.MapControllers();                  // 10. Execute endpoint

app.Run();

⚠️ Middleware order matters! UseAuthentication() phải trước UseAuthorization(). UseCors() phải sau UseRouting() nhưng trước UseAuthorization().


6. .NET 8/9 Highlights Cho Production

.NET 8 LTS (Long-Term Support)

csharp
// 1. Frozen Collections - hash table tối ưu cho read-only data
var lookup = new Dictionary<string, int>
{
    ["VN"] = 84, ["US"] = 1, ["JP"] = 81
}.ToFrozenDictionary(); // System.Collections.Frozen
// ~75% faster lookup so với Dictionary thường

// 2. Time abstraction - testable time
public class OrderService(TimeProvider timeProvider)
{
    public Order CreateOrder()
    {
        return new Order
        {
            CreatedAt = timeProvider.GetUtcNow(), // testable!
            ExpiresAt = timeProvider.GetUtcNow().AddHours(24)
        };
    }
}
// Test: new OrderService(new FakeTimeProvider(fixedTime))

// 3. Keyed Services DI
builder.Services.AddKeyedSingleton<ICache>("redis", new RedisCache());
builder.Services.AddKeyedSingleton<ICache>("memory", new MemoryCache());

public class ProductService([FromKeyedServices("redis")] ICache cache)
{
    // Inject Redis cache cụ thể
}

// 4. Native AOT cho ASP.NET Core
// Minimal APIs fully AOT-compatible trong .NET 8

.NET 9 (STS - Standard Term Support, Nov 2024)

csharp
// 1. LINQ improvements
var topProducts = products
    .CountBy(p => p.Category)     // Đếm theo group, không cần GroupBy().Select()
    .Where(g => g.Value > 10);

var indexed = items.Index();       // (index, element) pairs

// 2. System.Text.Json - mới
[JsonConverter(typeof(JsonStringEnumConverter<OrderStatus>))]
public enum OrderStatus { Pending, Shipped, Delivered }

// 3. Task.WhenEach - process tasks as they complete
await foreach (var task in Task.WhenEach(tasks))
{
    var result = await task;
    Console.WriteLine($"Completed: {result}");
}

// 4. HybridCache - thay MemoryCache + DistributedCache
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new()
    {
        LocalCacheExpiration = TimeSpan.FromMinutes(5),
        Expiration = TimeSpan.FromMinutes(30) // distributed
    };
});

public class ProductService(HybridCache cache)
{
    public async Task<Product> GetProduct(int id)
    {
        return await cache.GetOrCreateAsync(
            $"product:{id}",
            async ct => await _db.Products.FindAsync(id, ct),
            tags: ["products"]
        );
    }
}

// 5. Built-in OpenAPI (Scalar thay Swagger)
builder.Services.AddOpenApi();
app.MapOpenApi(); // /openapi/v1.json

7. Diagnostic Cheat Sheet

bash
# Xem runtime info
dotnet --info

# Xem GC stats realtime
dotnet-counters monitor --process-id <PID> \
    --counters System.Runtime[gen-0-gc-count,gen-1-gc-count,gen-2-gc-count,gc-heap-size]

# Capture trace (performance profiling)
dotnet-trace collect --process-id <PID> --duration 00:00:30

# Memory dump
dotnet-dump collect --process-id <PID>
dotnet-dump analyze <dump-file>

# HTTP benchmarking
dotnet tool install -g dotnet-httprepl

Tổng Kết Session 01

ConceptKey Takeaway
.NET Framework → .NET 9Framework chỉ còn support, mọi dự án mới dùng .NET 8/9
CoreCLRCross-platform, modular, hiệu năng cao hơn nhiều
Tiered JIT + PGOStartup nhanh (Tier 0) + throughput tối ưu (Tier 1 + PGO)
Native AOTStartup cực nhanh, phù hợp serverless/containers
GCServer GC cho web, DATAS (.NET 8+), tránh LOH allocation
KestrelProduction-ready edge server, HTTP/2 native
.NET 8/9FrozenCollection, HybridCache, TimeProvider, Native AOT

Bài tập: Chạy dotnet-counters trên 1 ASP.NET Core app của bạn, quan sát GC gen-0/1/2 counts khi load test bằng bombardier hoặc k6.