Remigiusz ZalewskiRemigiusz Zalewski

How to upgrade .NET application to .NET 10

upgrade-dotnet-application-to-dotnet-10

Introduction

Every November a new .NET version ships, and every November the same question comes up: is upgrading worth the risk right now? This video walks a real upgrade to .NET 10 - what's mechanical and safe to automate, what needs an actual human reading a changelog, and the tooling that handles most of the boring parts for you.

🎬 Watch the full video here:


Step one: the TargetFramework, and why that's not the hard part

<TargetFramework>net10.0</TargetFramework>

Changing this one line and hitting build is genuinely how the process starts - and for a small, dependency-light project, it might also be how it ends. For anything real, this is the step that reveals every other problem, since the compiler now checks against .NET 10's APIs and starts surfacing what actually needs attention.

NuGet packages are usually the real blocker

A TargetFramework bump means every referenced NuGet package needs to either already support .NET 10 or at minimum stay compatible via the .NET Standard/backward-compatibility story. dotnet list package --outdated is the first useful command to run - packages several major versions behind are the most likely source of build errors or runtime surprises after the bump:

dotnet list package --outdated
dotnet add package Microsoft.EntityFrameworkCore --version 10.0.0

A package with no .NET 10-compatible release yet is the actual blocker in most real upgrades - not the framework itself.

The .NET Upgrade Assistant automates the mechanical parts

Rather than hand-editing every .csproj, the Upgrade Assistant CLI tool walks a solution and applies the known-safe mechanical changes - target framework bumps, known package version updates, and some source-level API replacements for things that moved or got renamed:

dotnet tool install -g upgrade-assistant
upgrade-assistant upgrade MySolution.sln

It's genuinely good at the mechanical 80%. It does not replace reading the actual breaking-changes list for anything behavioral - a default that changed, a nullability annotation that's now stricter, an analyzer that now flags something it didn't before.

Read the breaking changes page, specifically

Every .NET release publishes a breaking changes document, organized by area (ASP.NET Core, EF Core, Runtime, SDK). This is the step that's easy to skip and the one most likely to bite you weeks later - a behavioral change that doesn't fail the build but quietly changes what your app does at runtime (a stricter validation default, a changed JSON serialization default) won't show up as a compile error at all.

Test before you trust it

A green build after an upgrade proves the code compiles against the new framework - it proves nothing about runtime behavior. Running the full test suite (and manually exercising anything undertested) against the upgraded app before merging is what actually catches the behavioral changes the compiler can't see.

Common pitfalls

  • Treating a green build as proof the upgrade is safe. It only proves compilation succeeded, not that behavior is unchanged.
  • Skipping the breaking-changes document because the build didn't flag anything - many breaking changes are behavioral, not compile-time.
  • Upgrading a huge monolith all at once instead of a smaller, lower-risk project first to build confidence in the process.
  • Not checking NuGet package compatibility before starting - discovering a blocking package mid-upgrade wastes the work already done.
  • Ignoring new analyzer warnings introduced by the upgrade instead of triaging what they're actually flagging.

Key Takeaways

  • Bumping TargetFramework is the easy first step; NuGet package compatibility is usually the actual blocker.
  • dotnet list package --outdated surfaces which dependencies need attention before the upgrade can proceed cleanly.
  • The .NET Upgrade Assistant automates the mechanical 80% of an upgrade but doesn't replace reading the breaking-changes documentation.
  • A green build proves compilation succeeded, not that runtime behavior is unchanged - run the full test suite before trusting the upgrade.

Get the Full Source Code

The complete runnable solution - the sample project before and after the .NET 10 upgrade - is available to Patreon supporters. If you want to see the actual diff instead of rebuilding the upgrade steps from the walkthrough above, you can find it on Patreon.

Resources