<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jasmin Omanovic</title>
    <description>The latest articles on DEV Community by Jasmin Omanovic (@jasmin_omanovic).</description>
    <link>https://dev.to/jasmin_omanovic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4234157%2F9a85d5d8-0bd8-4d7f-8989-cc0023de838d.jpg</url>
      <title>DEV Community: Jasmin Omanovic</title>
      <link>https://dev.to/jasmin_omanovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jasmin_omanovic"/>
    <language>en</language>
    <item>
      <title>Upgrading from .NET 8 to .NET 10: A Practical Checklist</title>
      <dc:creator>Jasmin Omanovic</dc:creator>
      <pubDate>Wed, 11 Nov 2026 21:07:42 +0000</pubDate>
      <link>https://dev.to/jasmin_omanovic/upgrading-from-net-8-to-net-10-a-practical-checklist-5g3g</link>
      <guid>https://dev.to/jasmin_omanovic/upgrading-from-net-8-to-net-10-a-practical-checklist-5g3g</guid>
      <description>&lt;h2&gt;
  
  
  Why upgrade now
&lt;/h2&gt;

&lt;p&gt;November 10, 2026 is the end-of-support date for both .NET 8 and .NET 9. Your apps keep running after that date, but they stop receiving security patches, so every new vulnerability stays open. The target is .NET 10, the current LTS release, supported until November 2028 (&lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-8-9-end-of-support/" rel="noopener noreferrer"&gt;Microsoft .NET Blog&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The good news: for most apps on modern .NET this is a days-long job, not a months-long one. The breaking changes are targeted and documented, and most of them show up as compiler warnings. This checklist is the order I'd follow for a typical solution, plus the changes most likely to bite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;Take inventory first, retarget second. Ten minutes here saves hours of guessing later.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;List every deployable project, test project and shared library in the solution.&lt;/li&gt;
&lt;li&gt;Find every place a version is pinned: &lt;code&gt;global.json&lt;/code&gt;, Dockerfiles, CI pipeline SDK versions, build scripts.&lt;/li&gt;
&lt;li&gt;Check what your machine and pipeline actually resolve:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet --list-sdks
dotnet --info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check your dependencies for outdated or abandoned packages:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet list package --outdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run your test suite on the current version and save the result. You want a known-good baseline to compare against.&lt;/li&gt;
&lt;li&gt;Create a dedicated branch. Keep the SDK bump, the package updates and the code fixes in separate commits so a failure is easy to locate.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The upgrade, step by step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Pin the SDK.&lt;/strong&gt; Install the .NET 10 SDK and update &lt;code&gt;global.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"sdk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"10.0.100"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"rollForward"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"latestFeature"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Change the target framework&lt;/strong&gt; in every project (or once in &lt;code&gt;Directory.Build.props&lt;/code&gt; if you centralize it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upgrade shared libraries first, then the apps that depend on them. If other teams consume your libraries, multi-target for a while with &lt;code&gt;&amp;lt;TargetFrameworks&amp;gt;net8.0;net10.0&amp;lt;/TargetFrameworks&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Update Microsoft packages to 10.0.x.&lt;/strong&gt; That means &lt;code&gt;Microsoft.AspNetCore.*&lt;/code&gt;, &lt;code&gt;Microsoft.Extensions.*&lt;/code&gt; and &lt;code&gt;Microsoft.EntityFrameworkCore.*&lt;/code&gt;. Keep their versions aligned with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Restore and read the new warnings.&lt;/strong&gt; &lt;code&gt;dotnet restore&lt;/code&gt; now audits transitive packages by default, so expect vulnerability warnings you didn't see before. NuGet also flags direct references it can prune (NU1510), and a &lt;code&gt;PackageReference&lt;/code&gt; without a version is now an error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Build with warnings as errors.&lt;/strong&gt; Obsolete APIs surface as SYSLIB and ASPDEPR diagnostics. This is the fastest way to find most of what needs fixing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet build -warnaserror
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Run the tests and the app.&lt;/strong&gt; Compare against your baseline. Many changes are behavioral, so a green build is not the finish line: run integration tests with real configuration and real payloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually breaks
&lt;/h2&gt;

&lt;p&gt;Coming from .NET 8 you inherit the breaking changes of both .NET 9 and .NET 10. These are the ones most likely to affect a typical web app; the full lists are linked in Sources.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WebHostBuilder&lt;/code&gt;, &lt;code&gt;IWebHost&lt;/code&gt; and &lt;code&gt;WebHost&lt;/code&gt; are obsolete&lt;/td&gt;
&lt;td&gt;Move to &lt;code&gt;WebApplication.CreateBuilder&lt;/code&gt; or the generic host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;IActionContextAccessor&lt;/code&gt; / &lt;code&gt;ActionContextAccessor&lt;/code&gt; are obsolete&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;IHttpContextAccessor&lt;/code&gt; instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WithOpenApi()&lt;/code&gt; extension method is deprecated&lt;/td&gt;
&lt;td&gt;Remove it; the built-in generator now emits OpenAPI 3.1 by default. Pin 3.0 if your client generators need it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;td&gt;Razor runtime compilation is obsolete&lt;/td&gt;
&lt;td&gt;Rely on build-time compilation and Hot Reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ForwardedHeadersOptions.KnownNetworks&lt;/code&gt; and &lt;code&gt;IPNetwork&lt;/code&gt; are obsolete&lt;/td&gt;
&lt;td&gt;Switch to &lt;code&gt;KnownIpNetworks&lt;/code&gt; with &lt;code&gt;System.Net.IPNetwork&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;td&gt;Cookie login redirects are disabled for known API endpoints&lt;/td&gt;
&lt;td&gt;API calls now get 401/403 instead of a redirect; check clients that expected the redirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blazor&lt;/td&gt;
&lt;td&gt;Response streaming is on by default for &lt;code&gt;HttpClient&lt;/code&gt; in the browser&lt;/td&gt;
&lt;td&gt;Opt out per request if code reads the whole body synchronously&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON Patch&lt;/td&gt;
&lt;td&gt;Now based on System.Text.Json instead of Newtonsoft&lt;/td&gt;
&lt;td&gt;Install the new package; avoid dynamic types like &lt;code&gt;ExpandoObject&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EF Core 10&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ExecuteUpdateAsync&lt;/code&gt; takes a regular lambda&lt;/td&gt;
&lt;td&gt;Rewrite hand-built expression trees as plain lambdas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EF Core 10&lt;/td&gt;
&lt;td&gt;EF tools need &lt;code&gt;--framework&lt;/code&gt; on multi-targeted projects&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;--framework net10.0&lt;/code&gt; to &lt;code&gt;dotnet ef&lt;/code&gt; commands while you multi-target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# 14&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;field&lt;/code&gt; is a keyword inside property accessors&lt;/td&gt;
&lt;td&gt;Rename members called &lt;code&gt;field&lt;/code&gt; or use &lt;code&gt;@field&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# 14&lt;/td&gt;
&lt;td&gt;Span overloads apply in more places&lt;/td&gt;
&lt;td&gt;Fix new ambiguity errors with an explicit cast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;No default SIGTERM handler is installed&lt;/td&gt;
&lt;td&gt;Add your own if you relied on it for graceful shutdown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BackgroundService&lt;/code&gt; runs all of &lt;code&gt;ExecuteAsync&lt;/code&gt; as a Task&lt;/td&gt;
&lt;td&gt;Synchronous startup code in &lt;code&gt;ExecuteAsync&lt;/code&gt; no longer blocks host startup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containers&lt;/td&gt;
&lt;td&gt;Default images use Ubuntu instead of Debian&lt;/td&gt;
&lt;td&gt;Fix Dockerfiles that use Debian package names or paths&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  CI/CD and Docker
&lt;/h2&gt;

&lt;p&gt;The code is only half the upgrade; the pipeline still builds with whatever SDK it was told to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'10.0.x'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure DevOps:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UseDotNet@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;packageType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'sdk'&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'10.0.x'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dockerfile:&lt;/strong&gt; bump both stages and remember the new Ubuntu base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt; mcr.microsoft.com/dotnet/sdk:10.0 AS build
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; mcr.microsoft.com/dotnet/aspnet:10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Dockerfile installs packages with &lt;code&gt;apt-get&lt;/code&gt;, check the package names still exist on Ubuntu. In automated scripts, pass &lt;code&gt;--interactive false&lt;/code&gt; to CLI commands, since the CLI now defaults to interactive mode for users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from .NET Framework
&lt;/h2&gt;

&lt;p&gt;A .NET Framework 4.x app is a migration, not a version bump. .NET Framework 4.8 is not part of the November 2026 deadline, since its support follows the Windows lifecycle, but staying on it means no new runtime features and a shrinking library ecosystem.&lt;/p&gt;

&lt;p&gt;What changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project files.&lt;/strong&gt; Convert old-style &lt;code&gt;.csproj&lt;/code&gt; files to SDK-style, and &lt;code&gt;packages.config&lt;/code&gt; to &lt;code&gt;PackageReference&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration.&lt;/strong&gt; &lt;code&gt;web.config&lt;/code&gt; / &lt;code&gt;app.config&lt;/code&gt; settings move to &lt;code&gt;appsettings.json&lt;/code&gt; and the options pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting and startup.&lt;/strong&gt; &lt;code&gt;Global.asax&lt;/code&gt; and &lt;code&gt;System.Web&lt;/code&gt; are gone; the pipeline is built in &lt;code&gt;Program.cs&lt;/code&gt; with middleware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency injection&lt;/strong&gt; is built in, so third-party containers become optional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No direct path for some technologies.&lt;/strong&gt; ASP.NET Web Forms, WCF server and Windows Workflow have no direct equivalent and need a partial rewrite (for example Razor Pages or Blazor for Web Forms, CoreWCF or gRPC for WCF).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The usual approach: move class libraries to .NET Standard 2.0 or multi-target them first, then migrate the web project, then remove the old one. For large apps, migrate incrementally route by route behind a reverse proxy instead of a big-bang rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Inventory projects, pinned SDKs, Dockerfiles and pipelines&lt;/li&gt;
&lt;li&gt;Baseline test run on the current version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;global.json&lt;/code&gt; pinned to the .NET 10 SDK&lt;/li&gt;
&lt;li&gt;All projects on &lt;code&gt;net10.0&lt;/code&gt;, shared libraries first&lt;/li&gt;
&lt;li&gt;Microsoft packages on 10.0.x, versions aligned&lt;/li&gt;
&lt;li&gt;New NuGet audit warnings reviewed&lt;/li&gt;
&lt;li&gt;Build clean with &lt;code&gt;-warnaserror&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Breaking changes from .NET 9 and .NET 10 reviewed&lt;/li&gt;
&lt;li&gt;CI/CD and Docker images on 10.0&lt;/li&gt;
&lt;li&gt;Tests and integration tests green, compared to baseline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Free tool: scan your solution first
&lt;/h2&gt;

&lt;p&gt;To get the inventory from the first step in one command, I wrote a free, open-source PowerShell script: &lt;a href="https://github.com/northbytelab/netupgradelab-scanner" rel="noopener noreferrer"&gt;NetUpgradeLab Scanner&lt;/a&gt;. It's read-only and produces a Markdown report of projects, outdated Microsoft packages, obsolete APIs and pinned SDK/Docker/CI versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pwsh&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;Invoke&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;NetUpgradeScan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ps1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;\&lt;span class="n"&gt;src&lt;/span&gt;\&lt;span class="nc"&gt;MySolution&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Need a hand?
&lt;/h2&gt;

&lt;p&gt;I'm Jasmin, a .NET developer with 6+ years of experience in C#, ASP.NET Core, Blazor and SQL Server. If you'd rather hand the upgrade off, I take on .NET 10 upgrades and .NET Framework migrations: &lt;a href="https://www.fiverr.com/jassoman/upgrade-your-dotnet-framework-or-dotnet-8-app-to-dotnet-10" rel="noopener noreferrer"&gt;Upgrade your app to .NET 10&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-8-9-end-of-support/" rel="noopener noreferrer"&gt;.NET 8 and .NET 9 will reach End of Support on November 10, 2026&lt;/a&gt; (Microsoft .NET Blog)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/10" rel="noopener noreferrer"&gt;Breaking changes in .NET 10&lt;/a&gt; (Microsoft Learn)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/breaking-changes/10/overview" rel="noopener noreferrer"&gt;Breaking changes in ASP.NET Core 10&lt;/a&gt; (Microsoft Learn)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes" rel="noopener noreferrer"&gt;Breaking changes in EF Core 10&lt;/a&gt; (Microsoft Learn)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>aspnetcore</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
