Modern .NET — Hosting, Configuration, DI & Deployment
In short: .NET Framework is the original Windows-only implementation (last version 4.8.1, maintenance only). .NET Core was a cross-platform, open-source, high-performance rewrite (2016–2019).
- From .NET 5 (2020) the platform is unified as just ".NET", the successor of .NET Core, with yearly November releases (even = LTS, 3 years; odd = STS, 2 years).
- New projects should target the current LTS, .NET 10.
In short: The Common Language Runtime is .NET's execution engine.
- It loads assemblies, verifies and JIT-compiles IL to native code, manages memory with the garbage collector, enforces type safety, handles exceptions, manages threads and the thread pool, and provides interop with native code.
- Code running under the CLR is "managed code".
In short: JIT (Just-In-Time) compiles IL to machine code at runtime, the first time each method is called.
- Tiered compilation first produces quick code, then re-optimises hot methods, with Dynamic PGO using live profiling.
- ReadyToRun ships precompiled code inside assemblies to cut startup time, while keeping JIT as a fallback.
- Native AOT compiles the entire app to a native executable at publish time: fastest startup, smallest memory, no JIT.
- The cost is no dynamic code generation and limited reflection, so it needs trimming-compatible libraries.
In short: LTS (Long-Term Support) releases ship in even-numbered years' November (.NET 6, 8, 10) and get 3 years of free patches.
- STS (Standard-Term Support) releases are odd-numbered (.NET 7, 9) and get 24 months of support (18 months before .NET 9).
- Enterprises usually standardise on LTS.
- Both .NET 8 and .NET 9 reach end of support on 10 November 2026, and .NET 10 is supported until November 2028.
In short: An assembly is a compiled unit (.dll/.exe) containing IL code, type metadata, a manifest (name, version, culture, references) and resources.
- It is the unit of deployment, versioning and security.
- The Global Assembly Cache was a machine-wide store for strongly named shared assemblies in .NET Framework.
- Modern .NET has no GAC: each app carries its dependencies, or uses the shared framework installed side by side.
In short: .NET Standard is a formal specification of APIs available on all .NET implementations (2.0 is the widely used version; 2.1 is not supported by .NET Framework).
- Target
netstandard2.0only if a library must also be consumed by .NET Framework apps. - Otherwise target modern TFMs like
net8.0/net10.0, or multi-target<TargetFrameworks>net48;net8.0</TargetFrameworks>.
In short: Default for WebApplication.CreateBuilder: appsettings.json → appsettings.{Environment}.json → User Secrets (Development only) → environment variables → command-line arguments.
- Later sources override earlier ones, so env vars in production override JSON files.
- Hierarchical keys use
:in code (Smtp:Host) and__in environment variables (Smtp__Host). - Production secrets belong in Azure Key Vault or the hosting platform's secret store, not in JSON.
In short: IOptions<T> is a singleton that reads values once at first use and never reloads.
IOptionsSnapshot<T>is scoped and recomputed per request, so it picks up config file changes between requests; it can't be injected into singletons.IOptionsMonitor<T>is a singleton that always returns the current value viaCurrentValueand offersOnChangecallbacks;- use it in singletons and background services.
In short: IHostedService has StartAsync/StopAsync, called by the host at startup and shutdown.
BackgroundServiceis a base class where you implementExecuteAsync(CancellationToken)for long-running loops: queue consumers, schedulers, outbox publishers.- It's registered as a singleton via
AddHostedService<T>(), so create DI scopes for scoped services. - Honour the stopping token for graceful shutdown.
- For complex schedules, use Quartz.NET or Hangfire.
In short: Kestrel is ASP.NET Core's built-in, cross-platform, high-performance web server.
- It can face the internet directly.
- A reverse proxy (IIS, Nginx, Azure Front Door, YARP) is still often used for TLS termination, port sharing, load balancing and static file caching.
- On Windows, IIS hosts ASP.NET Core through the ASP.NET Core Module, in-process (default, fastest) or out-of-process (proxying to Kestrel).
In short: Framework-dependent: small output, but requires the matching .NET runtime on the server.
- Ops can patch the runtime centrally.
- Self-contained: includes the runtime for a specific RID (e.g.
linux-x64), so nothing needs installing, but the output is bigger and you must republish to get runtime patches. - Containers make this mostly moot: the base image provides the runtime.
In short: It's a generational, mark-and-compact (tracing) collector.
- It marks objects reachable from roots, then sweeps or compacts Gen 0/1/2; the LOH holds objects of 85,000 bytes or more and is collected with Gen 2.
- Collections are triggered by allocation budgets.
- Workstation GC uses one heap, tuned for responsiveness on client apps.
- Server GC uses a heap and GC thread per core for throughput and is the ASP.NET Core default; DATAS in .NET 9+ adapts the heap count dynamically.
- Background GC collects Gen 2 concurrently while Gen 0/1 collections continue.
In short: No runtime code generation (Reflection.Emit, some dynamic proxies), limited reflection (trimming removes unused code, so you need source generators or annotations), no dynamic assembly loading, and a platform-specific build per OS/architecture.
- Some libraries are incompatible: classic EF Core full model building has AOT limitations, MVC controllers are not supported (Minimal APIs are).
- It's best for microservices, serverless functions and CLI tools where startup time and memory matter.
In short: Incrementally, with the Strangler Fig pattern.
- Put a new ASP.NET Core app with YARP in front, which proxies unknown routes to the old app.
- Use System.Web adapters to share session/auth.
- Move class libraries to
netstandard2.0/multi-target first, then migrate controllers and views route by route, replacingSystem.Webdependencies,HttpContext.Current,web.configsettings and Global.asax with modern equivalents. - Use the Upgrade Assistant for mechanical changes.
- EF6 can stay initially (it runs on modern .NET) and move to EF Core later.
- Ship each slice to production behind the proxy.
In short: .NET 8 reaches end of support on 10 November 2026, after which there are no security fixes.
- Plan the upgrade to .NET 10 (LTS, supported to Nov 2028) well before then.
- Bump the TargetFramework, update Microsoft.* and EF Core packages to 10.x, read the breaking-changes docs, run the full test suite, fix analyzer warnings, update the Docker base images (
aspnet:10.0) and CI SDK versions (global.json, theUseDotNettask), deploy to staging, run load tests, then roll out. - Budget time for third-party packages that lag behind.
In short: That points to thread-pool starvation: synchronous blocking (.Result, .Wait(), sync I/O, Thread.Sleep) inside request handling.
- Each blocked thread waits on I/O, the pool injects new threads slowly (about one or two per second), and requests queue.
- Confirm with
dotnet-counters(ThreadPool Queue Length and Thread Count rising) anddotnet-stack/a dump showing blocked threads. - Fix by going async end to end, using async DB/HTTP APIs, and removing sync-over-async.
In short: Check ASPNETCORE_ENVIRONMENT on the App Service (it decides which appsettings.{env}.json loads).
- App Service Connection strings and App settings become environment variables and override JSON.
- Check the key names (
ConnectionStrings__Db, or the connection-strings section with the right type). - Check slot settings after a swap and Key Vault references resolving (managed identity permissions).
- Log the configuration source (never the secret value) at startup, or use
builder.Configuration.GetDebugView()locally.