.NET Runtime Overview — CLR, JIT and the SDK vs Runtime

Before writing a single line of ASP.NET Core code, it pays to understand what is actually executing it. The Common Language Runtime (CLR) is the virtual machine that runs .NET applications — it manages memory through garbage collection, compiles Intermediate Language (IL) to native machine code through the Just-In-Time (JIT) compiler, and enforces type safety. The .NET SDK is the developer toolchain (compiler, CLI, project templates); the .NET Runtime is what end users and servers need to run your application. Understanding this distinction informs every deployment decision you make for your ASP.NET Core Web API.

The .NET Execution Pipeline

// Source code (.cs files)
//     ↓  Roslyn compiler (part of SDK)
// Intermediate Language (.dll — platform-independent bytecode)
//     ↓  CLR / JIT compiler (at runtime, on first call to each method)
// Native machine code (x64, ARM64 — executes directly on CPU)
//
// The JIT compiles methods on first call and caches native code.
// ReadyToRun (R2R) and Native AOT pre-compile to native at publish time.
// Profile-guided optimisation (PGO) in .NET 6+ re-optimises hot methods.

// ── Target Framework Monikers (TFMs) ──────────────────────────────────────
// net8.0           = cross-platform .NET 8
// net8.0-windows   = .NET 8 with Windows-specific APIs
// net8.0-android   = .NET 8 for Android (MAUI)
// netstandard2.0   = shared library compatible with .NET Framework and .NET Core

// ── Runtime Identifiers (RIDs) ────────────────────────────────────────────
// linux-x64    = 64-bit Linux (most cloud servers)
// linux-arm64  = 64-bit ARM Linux (AWS Graviton, Apple M1 Linux)
// win-x64      = 64-bit Windows
// osx-arm64    = macOS Apple Silicon (M1/M2/M3)
// osx-x64      = macOS Intel
Note: There are two deployment models for .NET applications. Framework-dependent deployments produce a small set of DLLs that require the .NET Runtime to be pre-installed on the target machine — the DLL is small but the server must have .NET installed. Self-contained deployments bundle the entire .NET runtime alongside your application DLLs — larger package, but the server needs nothing pre-installed. For Docker deployments (the norm for ASP.NET Core Web APIs), framework-dependent is preferred because base images like mcr.microsoft.com/dotnet/aspnet:8.0 already include the runtime, keeping image sizes small.
Tip: For cloud-native ASP.NET Core deployments, use ReadyToRun (R2R) publishing (dotnet publish -c Release --runtime linux-x64 /p:PublishReadyToRun=true). R2R pre-compiles IL to native code at publish time, dramatically reducing cold-start time (the time for JIT to warm up on the first request). This is critical for container-based deployments where new instances start under load. On serverless platforms (Azure Container Apps, AWS Fargate), cold-start latency directly affects user experience.
Warning: Native AOT (Ahead-of-Time compilation, available in .NET 7+) produces fully native executables with no JIT and minimal runtime footprint — ideal for serverless and edge deployments. However, Native AOT has significant restrictions: no runtime code generation, limited reflection (requires source generators like JsonSerializerContext), and not all NuGet packages support it. Before committing to Native AOT for a new project, verify that all your key dependencies (EF Core, Serilog, FluentValidation) have AOT-compatible versions.

SDK vs Runtime vs ASP.NET Core Runtime

Component Contains Needed For
.NET SDK Compiler, CLI, templates, build tools Developers building apps
.NET Runtime CLR, base class libraries (BCL) Running any .NET app
ASP.NET Core Runtime .NET Runtime + web framework Running ASP.NET Core apps

Common Mistakes

Mistake 1 — Installing only .NET SDK in production (waste and security risk)

❌ Wrong — SDK includes compilers and tools that are not needed at runtime and increase attack surface.

✅ Correct — install only the ASP.NET Core Runtime in production; use SDK only in CI/CD build environments.

Mistake 2 — Mixing TargetFramework TFMs in a multi-project solution

❌ Wrong — Domain targets net8.0 while Infrastructure targets net6.0 — incompatible assemblies.

✅ Correct — set TargetFramework in Directory.Build.props to ensure all projects use the same TFM.

🧠 Test Yourself

What is the difference between a framework-dependent and a self-contained .NET deployment?