deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Upgrading off .NET 8 and 9 before 10 November 2026 spans more than one file

.NET 8, .NET 9 and Azure Functions' in-process model all reach end of support on 10 November 2026. A dev.to post maps the files that can keep a repo pinned to an unsupported runtime.

Upgrading off .NET 8 and 9 before 10 November 2026 spans more than one file

Two runtimes expire on the same day

On 10 November 2026, .NET 8 and .NET 9 both reach end of support, and the in-process hosting model for Azure Functions expires alongside them. According to a dev.to post published in late October, nothing stops working the next day — but every vulnerability found in those runtimes from then on goes unpatched.

The natural destination is .NET 10, the current LTS release, supported until November 2028. For a simple project the upgrade can be one edit: changing TargetFramework from net8.0 to net10.0 in the project file. The catch, the post argues, is that the runtime an application actually builds and runs on is rarely decided by that line. In a typical repository the version is spread across six or seven files, any one of which can leave a project on .NET 8 after the developer believes the move is done.

Where the version actually lives

The obvious location is TargetFramework in each .csproj, .fsproj or .vbproj. The post lists less obvious ones:

  • TargetFrameworks, plural, for multi-targeted libraries such as net8.0;netstandard2.0.
  • Properties: many solutions set TargetFramework to a variable like $(AppTfm) defined in a Directory.Build.props file one or two folders up, so searching project files for net8.0 finds nothing.
  • Old-style projects using TargetFrameworkVersion such as v4.6.1 are .NET Framework — a different and larger migration. SDK-style projects targeting net48 or net462 are also .NET Framework; 4.6.2 ends support on 12 January 2027.

Where the TFM comes from a shared property, the post advises changing it once in Directory.Build.props rather than in every project.

SDK, container and pipeline pins

Three more places commonly hold a version.

global.json pins the build SDK: a file specifying 8.0.404 makes a machine with only the .NET 10 SDK refuse to build. The replacement needs a full version number such as 10.0.100, and rollForward set to latestFeature avoids editing the file on every patch release.

Dockerfiles reference versioned base images in both the SDK and runtime stages, and both must move to 10.0. The post flags several knock-on effects: default .NET 10 tags are Ubuntu 24.04 rather than Debian, so apt-get steps may need different package names; since .NET 8 the default container port is 8080 rather than 80, which affects port mappings in EXPOSE, compose files, Kubernetes manifests and App Service; and .NET 9 images no longer include zlib. Compose files can also carry their own image references, sometimes behind a variable tag.

CI pipelines install whatever SDK they request — dotnet-version in GitHub Actions' setup-dotnet, or the version in Azure Pipelines' UseDotNet@2 — and matrix builds are easy to overlook. On packages, the author advises moving the Microsoft.AspNetCore., EntityFrameworkCore. and Extensions.* families to 10.0.x and otherwise upgrading third-party packages only where necessary, noting that the .NET 10 SDK audits transitive packages during restore, which can turn previously green builds red when warnings are treated as errors.

Azure Functions carry the heavy lift

The item that turns the upgrade into real work, according to the post, is Azure Functions: the in-process model ends support on the same date, and .NET 10 functions run only on the isolated worker model. Migration means replacing Microsoft.NET.Sdk.Functions with worker packages, swapping each WebJobs extension for its Functions.Worker equivalent, moving configuration from FunctionsStartup into a Program.cs, and changing [FunctionName] to [Function]. Serialization switches to System.Text.Json, and Newtonsoft's [JsonProperty] attributes are ignored without an error — a function keeps running while reading and writing different property names. The FUNCTIONS_WORKER_RUNTIME setting and the new code must be deployed together, ideally through a staging slot; changing one without the other produces AZFD0013 errors.

Changes that compile but behave differently

Moving from 8 to 10 absorbs two releases of breaking changes, and the ones that never surface as build errors are the risky ones. The post's shortlist: BinaryFormatter always throws since 9; synchronous work at the top of BackgroundService.ExecuteAsync no longer delays startup; configuration keeps null values; cookie authentication returns 401 rather than redirecting to login for API endpoints; and in Development the host validates DI scopes at build time, exposing latent bugs locally. EF Core 9 throws if migrations are applied while the model has pending changes, and EF Core 10 on Azure SQL may generate a migration converting JSON columns to a new json type — the author suggests running dotnet ef migrations add after upgrading and reading the result before deploying anything. The post closes with the author's open-source scanner, dotnet-eol-scan, which reads these files and lists every .NET version it finds with its end-of-support date.

Why it matters

Two supported runtimes expire simultaneously, so a large slice of the .NET ecosystem stops receiving security fixes on the same date. The post's central point is that this migration is easy to start and easy to leave half-finished: a repo can have updated project files yet still build with an old SDK in CI or ship in an old container image, and the gap stays invisible until a vulnerability lands in a runtime that no longer gets patches. Treating the move as a repository-wide audit — TFMs, SDK pins, container images, pipelines and Functions hosting — is what separates a completed upgrade from a cosmetic one.

  • #dotnet
  • #azure-functions
  • #docker
  • #security
  • #upgrades

Related posts