Claude Cursor GitHub Copilot Skill

migrate-dotnet8-to-dotnet9

Migrate a .NET 8 project to .NET 9 and resolve all breaking changes. USE FOR: upgrading TargetFramework from net8.0 to net9.0, fixing build errors after updating the .NET 9 SDK, resolving behavioral changes in .NET 9 / C# 13 / ASP.NET Core 9 / EF Core 9, replacing BinaryFormatter

LLM Mart · 0 points · 17 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download dotnet-skills-plugins_dotnet-upgrade_skills_migrate-dotnet8-to-dotnet9-98f8485.zip · 23 KB
Part of dotnet/skills — 119 skills

Install

skills CLI npx skills add https://github.com/dotnet/skills/tree/main/plugins/dotnet-upgrade/skills/migrate-dotnet8-to-dotnet9
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install dotnet-skills@llmmart
Git git clone https://github.com/dotnet/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole dotnet/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

.NET 8 → .NET 9 Migration

Migrate a .NET 8 project or solution to .NET 9, systematically resolving all breaking changes. The outcome is a project targeting net9.0 that builds cleanly, passes tests, and accounts for every behavioral, source-incompatible, and binary-incompatible change introduced in the .NET 9 release.

When to Use

  • Upgrading TargetFramework from net8.0 to net9.0
  • Resolving build errors or new warnings after updating the .NET 9 SDK
  • Adapting to behavioral changes in .NET 9 runtime, ASP.NET Core 9, or EF Core 9
  • Replacing BinaryFormatter usage (now always throws at runtime)
  • Updating CI/CD pipelines, Dockerfiles, or deployment scripts for .NET 9

When Not to Use

  • The project already targets net9.0 and builds cleanly — migration is done. If the goal is to reach net10.0, use the migrate-dotnet9-to-dotnet10 skill as the next step.
  • Upgrading from .NET 7 or earlier — address the prior version breaking changes first
  • Migrating from .NET Framework — that is a separate, larger effort
  • Greenfield projects that start on .NET 9 (no migration needed)

Inputs

Input Required Description
Project or solution path Yes The .csproj, .sln, or .slnx entry point to migrate
Build command No How to build (e.g., dotnet build, a repo build script). Auto-detect if not provided
Test command No How to run tests (e.g., dotnet test). Auto-detect if not provided
Project type hints No Whether the project uses ASP.NET Core, EF Core, WinForms, WPF, containers, etc. Auto-detect from PackageReferences and SDK attributes if not provided

Workflow

Answer directly from the loaded reference documents. Do not search the filesystem or fetch web pages for breaking change information — the references contain the authoritative details. Focus on identifying which breaking changes apply and providing concrete fixes.

Commit strategy: Commit at each logical boundary — after updating the TFM (Step 2), after resolving build errors (Step 3), after addressing behavioral changes (Step 4), and after updating infrastructure (Step 5). This keeps each commit focused and reviewable.

Step 1: Assess the project

  1. Identify how the project is built and tested. Look for build scripts, .sln/.slnx files, or individual .csproj files.
  2. Run dotnet --version to confirm the .NET 9 SDK is installed. If it is not, stop and inform the user.
  3. Determine which technology areas the project uses by examining:
    • SDK attribute: Microsoft.NET.Sdk.Web → ASP.NET Core; Microsoft.NET.Sdk.WindowsDesktop with <UseWPF> or <UseWindowsForms> → WPF/WinForms
    • PackageReferences: Microsoft.EntityFrameworkCore.* → EF Core; Microsoft.Extensions.Http → HttpClientFactory
    • Dockerfile presence → Container changes relevant
    • P/Invoke or native interop usage → Interop changes relevant
    • BinaryFormatter usage → Serialization migration needed
    • System.Text.Json usage → Serialization changes relevant
    • X509Certificate constructors → Cryptography changes relevant
  4. Record which reference documents are relevant (see the reference loading table in Step 3).
  5. Do a clean build (dotnet build --no-incremental or delete bin/obj) on the current net8.0 target to establish a clean baseline. Record any pre-existing warnings.

Step 2: Update the Target Framework

  1. In each .csproj (or Directory.Build.props if centralized), change:

    <TargetFramework>net8.0</TargetFramework>
    

    to:

    <TargetFramework>net9.0</TargetFramework>
    

    For multi-targeted projects, add net9.0 to <TargetFrameworks> or replace net8.0.

  2. Update all Microsoft.Extensions.*, Microsoft.AspNetCore.*, Microsoft.EntityFrameworkCore.*, and other Microsoft package references to their 9.0.x versions. If using Central Package Management (Directory.Packages.props), update versions there.

  3. Run dotnet restore. Watch for:

    • Version requirements: .NET 9 SDK requires Visual Studio 17.12+ to target net9.0 (17.11 for net8.0 and earlier).
    • New warnings for .NET Standard 1.x and .NET 7 targets — consider updating or removing outdated target frameworks.
  4. Run a clean build. Collect all errors and new warnings. These will be addressed in Step 3.

Step 3: Resolve build errors and source-incompatible changes

Work through compilation errors and new warnings systematically. Load the appropriate reference documents based on the project type:

If the project uses… Load reference
Any .NET 9 project references/csharp-compiler-dotnet8to9.md
Any .NET 9 project references/core-libraries-dotnet8to9.md
Any .NET 9 project references/sdk-msbuild-dotnet8to9.md
ASP.NET Core references/aspnet-core-dotnet8to9.md
Entity Framework Core references/efcore-dotnet8to9.md
Cryptography APIs references/cryptography-dotnet8to9.md
System.Text.Json, HttpClient, networking references/serialization-networking-dotnet8to9.md
Windows Forms or WPF references/winforms-wpf-dotnet8to9.md
Docker containers, native interop references/containers-interop-dotnet8to9.md
Runtime configuration, deployment references/deployment-runtime-dotnet8to9.md

Common source-incompatible changes to check for:

  1. params span overload resolution — New params ReadOnlySpan<T> overloads on String.Join, String.Concat, Path.Combine, Task.WhenAll, and many more now bind preferentially. Code calling these methods inside Expression lambdas will fail (CS8640/CS9226). See references/core-libraries-dotnet8to9.md.

  2. StringValues ambiguous overload — The params Span<T> feature creates ambiguity with StringValues implicit operators on methods like String.Concat, String.Join, Path.Combine. Fix by explicitly casting arguments. See references/core-libraries-dotnet8to9.md.

  3. New obsoletion warnings (SYSLIB0054–SYSLIB0057):

    • SYSLIB0054: Replace Thread.VolatileRead/VolatileWrite with Volatile.Read/Volatile.Write
    • SYSLIB0057: Replace X509Certificate2/X509Certificate binary/file constructors with X509CertificateLoader methods
    • Also SYSLIB0055 (ARM AdvSimd signed overloads) and SYSLIB0056 (Assembly.LoadFrom with hash algorithm) — see references/core-libraries-dotnet8to9.md
  4. C# 13 InlineArray on record structs — [InlineArray] attribute on record struct types is now disallowed (CS9259). Change to a regular struct. See references/csharp-compiler-dotnet8to9.md.

  5. C# 13 iterator safe context — Iterators now introduce a safe context in C# 13. Local functions inside iterators that used unsafe code inherited from an outer unsafe class will now error. Add unsafe modifier to the local function. See references/csharp-compiler-dotnet8to9.md.

  6. C# 13 collection expression overload resolution — Empty collection expressions ([]) no longer use span vs non-span to tiebreak overloads. Exact element type is now preferred. See references/csharp-compiler-dotnet8to9.md.

  7. String.Trim(params ReadOnlySpan<char>) removed — Code compiled against .NET 9 previews that passes ReadOnlySpan<char> to Trim/TrimStart/TrimEnd must rebuild; the overload was removed in GA. See references/core-libraries-dotnet8to9.md.

  8. BinaryFormatter always throws — If the project uses BinaryFormatter, stop and inform the user — this is a major decision. See references/serialization-networking-dotnet8to9.md.

  9. HttpListenerRequest.UserAgent is nullable — The property is now string?. Add null checks. See references/serialization-networking-dotnet8to9.md.

  10. Windows Forms nullability annotation changes — Some WinForms API parameters changed from nullable to non-nullable. Update call sites. See references/winforms-wpf-dotnet8to9.md.

  11. Windows Forms security analyzers (WFO1000) — New analyzers produce errors for properties without explicit serialization configuration. See references/winforms-wpf-dotnet8to9.md.

Build again after each batch of fixes. Repeat until the build is clean.

Step 4: Address behavioral changes

Behavioral changes do not cause build errors but may change runtime behavior. Review each applicable item and determine whether the previous behavior was relied upon.

High-impact behavioral changes (check first):

  1. Floating-point to integer conversions are now saturating — Conversions from float/double to integer types now saturate instead of wrapping on x86/x64. See references/deployment-runtime-dotnet8to9.md.

  2. EF Core: Pending model changes exception — Migrate()/MigrateAsync() now throws if the model has pending changes. Search for DateTime.Now, DateTime.UtcNow, or Guid.NewGuid() in any HasData call — these must be replaced with fixed constants (e.g., new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc)). See references/efcore-dotnet8to9.md.

  3. EF Core: Explicit transaction exception — Migrate() inside a user transaction now throws. See references/efcore-dotnet8to9.md.

  4. HttpClientFactory uses SocketsHttpHandler by default — Code that casts the primary handler to HttpClientHandler will get InvalidCastException. See references/serialization-networking-dotnet8to9.md.

  5. HttpClientFactory header redaction by default — All header values in Trace-level logs are now redacted. See references/serialization-networking-dotnet8to9.md.

  6. Environment variables take precedence over runtimeconfig.json — Runtime configuration settings from environment variables now override runtimeconfig.json. See references/deployment-runtime-dotnet8to9.md.

  7. ASP.NET Core ValidateOnBuild/ValidateScopes in development — HostBuilder now enables DI validation in development by default. See references/aspnet-core-dotnet8to9.md.

Other behavioral changes to review (may cause runtime exceptions ⚠️ or subtle behavioral differences):

  • ⚠️ FromKeyedServicesAttribute no longer injects non-keyed service fallback — throws InvalidOperationException
  • ⚠️ Container images no longer install zlib — apps depending on system zlib will fail
  • ⚠️ Intel CET is now enabled by default — non-CET-compatible native libraries may cause process termination
  • BigInteger now has a maximum length of (2^31) - 1 bits
  • JsonDocument deserialization of JSON null now returns non-null JsonDocument with JsonValueKind.Null instead of C# null
  • System.Text.Json metadata reader now unescapes metadata property names
  • ZipArchiveEntry names/comments now respect the UTF-8 flag
  • IncrementingPollingCounter initial callback is now asynchronous
  • InMemoryDirectoryInfo prepends rootDir to files
  • RuntimeHelpers.GetSubArray returns a different type
  • PictureBox raises HttpRequestException instead of WebException
  • StatusStrip uses a different default renderer
  • IMsoComponent support is opt-in
  • SafeEvpPKeyHandle.DuplicateHandle up-refs the handle
  • HttpClient metrics report server.port unconditionally
  • URI query strings redacted in HttpClient EventSource events and IHttpClientFactory logs
  • dotnet watch is incompatible with Hot Reload for old frameworks
  • WPF GetXmlNamespaceMaps returns Hashtable instead of String

Step 5: Update infrastructure

  1. Dockerfiles: Update base images. Note that .NET 9 container images no longer install zlib. If your app depends on zlib, add RUN apt-get update && apt-get install -y zlib1g to your Dockerfile.

    # Before
    FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
    FROM mcr.microsoft.com/dotnet/aspnet:8.0
    # After
    FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
    FROM mcr.microsoft.com/dotnet/aspnet:9.0
    
  2. CI/CD pipelines: Update SDK version references. If using global.json, update:

    {
      "sdk": {
        "version": "9.0.100",
        "rollForward": "latestFeature"
      }
    }
    

    Review the rollForward policy — if set to "disable" or "latestPatch", the SDK may not resolve correctly after upgrading. "latestFeature" (recommended) allows the SDK to roll forward to the latest 9.0.x feature band.

  3. Visual Studio version: .NET 9 SDK requires VS 17.12+ to target net9.0. VS 17.11 can only target net8.0 and earlier.

  4. Terminal Logger: dotnet build now uses Terminal Logger by default in interactive terminals. CI scripts that parse MSBuild console output may need --tl:off or MSBUILDTERMINALLOGGER=off.

  5. dotnet workload output: Output format has changed. Update any scripts that parse workload command output.

  6. .NET Monitor images: Tags simplified to version-only (affects container orchestration referencing specific tags).

Step 6: Verify

  1. Run a full clean build: dotnet build --no-incremental
  2. Run all tests: dotnet test
  3. If the application is containerized, build and test the container image
  4. Smoke-test the application, paying special attention to:
    • BinaryFormatter usage (will throw at runtime)
    • Floating-point to integer conversion behavior
    • EF Core migration application
    • HttpClientFactory handler casting and logging
    • DI validation in development environment
    • Runtime configuration settings (environment variable precedence)
  5. Review the diff and ensure no unintended behavioral changes were introduced

Reference Documents

The references/ folder contains detailed breaking change information organized by technology area. Load only the references relevant to the project being migrated:

Reference file When to load
references/csharp-compiler-dotnet8to9.md Always (C# 13 compiler breaking changes — InlineArray on records, iterator safe context, collection expression overloads)
references/core-libraries-dotnet8to9.md Always (applies to all .NET 9 projects)
references/sdk-msbuild-dotnet8to9.md Always (SDK and build tooling changes)
references/aspnet-core-dotnet8to9.md Project uses ASP.NET Core
references/efcore-dotnet8to9.md Project uses Entity Framework Core
references/cryptography-dotnet8to9.md Project uses System.Security.Cryptography or X.509 certificates
references/serialization-networking-dotnet8to9.md Project uses BinaryFormatter, System.Text.Json, HttpClient, or networking APIs
references/winforms-wpf-dotnet8to9.md Project uses Windows Forms or WPF
references/containers-interop-dotnet8to9.md Project uses Docker containers or native interop (P/Invoke)
references/deployment-runtime-dotnet8to9.md Project uses runtime configuration, deployment, or has floating-point to integer conversions
Files (skills)
  • references
    • aspnet-core-dotnet8to9.md 2.3 KB
      # ASP.NET Core 9 Breaking Changes
      
      These changes affect projects using ASP.NET Core (Microsoft.NET.Sdk.Web).
      
      ## Behavioral Changes
      
      ### HostBuilder enables ValidateOnBuild/ValidateScopes in development
      
      **Impact: Medium.** In the development environment, `ValidateOnBuild` and `ValidateScopes` are now enabled by default when options haven't been set with `UseDefaultServiceProvider`. This means DI registration issues that were previously silent will now throw at startup in development.
      
      **Mitigation:** Fix the DI registration issues (recommended), or disable validation:
      ```csharp
      builder.Host.UseDefaultServiceProvider(options =>
      {
          options.ValidateOnBuild = false;
          options.ValidateScopes = false;
      });
      ```
      
      ### Middleware types with multiple constructors
      
      **Impact: Low.** Middleware activation behavior has changed when a middleware type has multiple constructors. Previously the behavior was undefined; now it selects the most appropriate constructor.
      
      ### Forwarded Headers Middleware ignores X-Forwarded-* headers from unknown proxies
      
      **Impact: Medium.** The `ForwardedHeadersMiddleware` now ignores `X-Forwarded-*` headers from proxies not in the `KnownProxies` or `KnownNetworks` list.
      
      **Mitigation:** Add your trusted proxy addresses to `KnownProxies` or `KnownNetworks`:
      ```csharp
      services.Configure<ForwardedHeadersOptions>(options =>
      {
          options.KnownProxies.Add(IPAddress.Parse("10.0.0.1"));
      });
      ```
      
      > **Warning:** Do not clear `KnownProxies` or `KnownNetworks` to accept forwarded headers from any source. This disables ASP.NET Core's protection against spoofed `X-Forwarded-*` headers and is unsafe for production. Only register the specific proxy addresses or network ranges your infrastructure uses.
      
      ## Source-Incompatible Changes
      
      ### Legacy Mono and Emscripten APIs not exported to global namespace
      
      **Impact: Low.** Legacy Mono and Emscripten interop APIs are no longer exported to the global namespace in Blazor WebAssembly. Use the specific namespace imports.
      
      ### DefaultKeyResolution.ShouldGenerateNewKey altered meaning
      
      The meaning of `DefaultKeyResolution.ShouldGenerateNewKey` has changed. Review code that checks this property.
      
      ### Dev cert export no longer creates folder
      
      `dotnet dev-certs https --export-path` no longer automatically creates the target folder. Ensure the directory exists before exporting.
      
    • containers-interop-dotnet8to9.md 1.9 KB
      # Containers and Interop Breaking Changes (.NET 9)
      
      These changes affect Docker containers, native interop, and CET (Control-flow Enforcement Technology).
      
      ## Containers
      
      ### Container images no longer install zlib
      
      **Impact: Medium.** .NET 9 container images no longer install `zlib` because the .NET Runtime now includes a statically linked `zlib-ng`. If your app has a direct dependency on the `zlib` system package, install it manually:
      
      ```dockerfile
      FROM mcr.microsoft.com/dotnet/aspnet:9.0
      RUN apt-get update && apt-get install -y zlib1g
      ```
      
      ### .NET Monitor images simplified to version-only tags
      
      **Impact: Low.** .NET Monitor container image tags have been simplified to version-only format. Update any container orchestration configuration that references specific tag formats.
      
      ## Interop
      
      ### CET supported by default
      
      **Impact: Medium (binary incompatible).** `apphost` and `singlefilehost` are now compiled with the `/CETCOMPAT` flag, enabling Intel CET (Control-flow Enforcement Technology) hardware-enforced stack protection. This enhances security against ROP exploits but imposes restrictions on shared libraries:
      
      - Libraries cannot set thread context to locations not on the shadow stack
      - Libraries cannot use exception handlers that jump to unlisted continuation addresses
      - Non-CET-compatible native libraries loaded via P/Invoke may cause process termination
      
      **Mitigation:** If a native library is incompatible:
      ```xml
      <!-- Opt out of CET in project file -->
      <PropertyGroup>
        <CETCompat>false</CETCompat>
      </PropertyGroup>
      ```
      
      > **Warning:** Disabling CET removes hardware-enforced control-flow integrity, reducing protection against ROP (return-oriented programming) and JOP (jump-oriented programming) exploits. Only disable CET after confirming the specific native library is incompatible, and re-enable it once the library is updated. Prefer per-application opt-out via Windows Security / group policy over a project-wide setting when possible.
      
    • core-libraries-dotnet8to9.md 7 KB
      # Core .NET Libraries Breaking Changes (.NET 9)
      
      These breaking changes affect all .NET 9 projects regardless of application type.
      
      ## Source-Incompatible Changes
      
      ### C# overload resolution prefers `params` span-type overloads
      
      **Impact: High.** .NET 9 added `params ReadOnlySpan<T>` overloads to many core methods. C# 13 overload resolution prefers `params Span<T>`/`params ReadOnlySpan<T>` over `params T[]`. This causes errors inside `Expression` lambdas, which cannot contain `ref struct` types.
      
      Affected methods include: `String.Join`, `String.Concat`, `String.Format`, `String.Split`, `Path.Combine`, `Path.Join`, `Task.WhenAll`, `Task.WhenAny`, `Task.WaitAll`, `Console.Write`, `Console.WriteLine`, `StringBuilder.AppendFormat`, `StringBuilder.AppendJoin`, `CancellationTokenSource.CreateLinkedTokenSource`, `ImmutableArray.Create`, `Delegate.Combine`, `JsonArray` constructors, `JsonTypeInfoResolver.Combine`, and more.
      
      ```csharp
      // BREAKS — CS8640/CS9226 inside Expression lambda
      Expression<Func<string, string, string>> join =
          (x, y) => string.Join("", x, y);  // binds to ReadOnlySpan overload
      ```
      
      **Fix:** Pass an explicit array to force binding to the `params T[]` overload:
      ```csharp
      Expression<Func<string, string, string>> join =
          (x, y) => string.Join("", new string[] { x, y });
      ```
      
      ### Ambiguous overload resolution affecting StringValues
      
      **Impact: Medium.** `StringValues` (from `Microsoft.Extensions.Primitives`) has implicit operators for `string` and `string[]` that conflict with the new `params Span<T>` overloads. The compiler throws CS0121 for ambiguous calls.
      
      **Fix:** Explicitly cast arguments to the appropriate type or use named parameters.
      
      ### New TimeSpan.From*() overloads that take integers
      
      **Impact: Low (primarily F#).** New integer overloads of `TimeSpan.FromDays`, `FromHours`, `FromMinutes`, etc. cause ambiguity in F# code. Specify the argument type to select the correct overload.
      
      ### String.Trim(params ReadOnlySpan<char>) overload removed
      
      **Impact: Medium.** The `Trim`, `TrimStart`, and `TrimEnd` overloads accepting `ReadOnlySpan<char>` were added in .NET 9 previews but removed in GA because they caused behavioral changes with common extension methods (e.g., `"prefixinfixsuffix".TrimEnd("suffix")` would change behavior).
      
      Code compiled against .NET 9 previews that explicitly passes `ReadOnlySpan<char>` to these overloads may fail with `MissingMethodException` at runtime when run on the GA runtime, or fail to compile when retargeted to .NET 9 GA. Code using `params char[]` continues to work.
      
      ```csharp
      // BREAKS — compiled against .NET 9 Preview with ReadOnlySpan<char> overloads
      static string TrimLogEntry(string str)
      {
          ReadOnlySpan<char> trimChars = [';', ',', '.'];
          return str.Trim(trimChars); // calls Trim(ReadOnlySpan<char>) in previews only
      }
      
      // Fix — target GA and use char[] so Trim binds to existing overloads
      static string TrimLogEntry(string str)
      {
          char[] trimChars = [';', ',', '.'];
          return str.Trim(trimChars); // calls Trim(char[]) / Trim(params char[])
      }
      ```
      
      > **Note:** Assemblies compiled against .NET 9 Preview 6 through RC2 must be recompiled to avoid `MissingMethodException` at runtime.
      
      ### API obsoletions (SYSLIB0054–SYSLIB0057)
      
      | Diagnostic | What's obsolete | Replacement |
      |------------|----------------|-------------|
      | SYSLIB0054 | `Thread.VolatileRead`/`Thread.VolatileWrite` | `Volatile.Read`/`Volatile.Write` |
      | SYSLIB0055 | `AdvSimd.ShiftRightLogicalRoundedNarrowingSaturate*` signed overloads | Unsigned overloads |
      | SYSLIB0056 | `Assembly.LoadFrom` with `AssemblyHashAlgorithm` | Overloads without `AssemblyHashAlgorithm` |
      | SYSLIB0057 | `X509Certificate2`/`X509Certificate` binary/file constructors and `X509Certificate2Collection.Import` | `X509CertificateLoader` methods |
      
      These use custom diagnostic IDs — suppressing `CS0618` does not suppress them.
      
      ### New version of some OOB packages
      
      **Impact: Low.** Some out-of-band packages have updated major versions. Review and update your package references.
      
      ## Behavioral Changes
      
      ### BinaryFormatter always throws
      
      **Impact: High.** `BinaryFormatter.Serialize` and `BinaryFormatter.Deserialize` now always throw `NotSupportedException` regardless of any configuration. The `EnableUnsafeBinaryFormatterSerialization` AppContext switch has been removed. See `serialization-networking-dotnet8to9.md` for full details.
      
      ### BigInteger maximum length restriction
      
      **Impact: Low.** `BigInteger` is now limited to `(2^31) - 1` bits (approximately 2.14 billion bits / ~256 MB). Values exceeding this throw `OverflowException`.
      
      ### Default InlineArray Equals() and GetHashCode() throw
      
      `Equals()` and `GetHashCode()` on types marked with `[InlineArrayAttribute]` now throw instead of returning incorrect results.
      
      ### Inline array struct size limit is enforced
      
      **Impact: Low.** `[InlineArray]` structs with a byte size exceeding 1 MiB (1,048,576 bytes) now fail to load at runtime.
      
      ### Creating type of array of System.Void not allowed
      
      Attempting to create `typeof(void[])` or similar constructs now throws `TypeLoadException`.
      
      ### EnumConverter validates registered types
      
      `EnumConverter` now validates that the type passed is actually an enum, throwing for non-enum types.
      
      ### FromKeyedServicesAttribute no longer injects non-keyed parameter
      
      **Impact: Medium.** When `[FromKeyedServices("key")]` is used and the keyed service isn't registered, it no longer falls back to injecting a non-keyed service. Instead, `InvalidOperationException` is thrown.
      
      ### IncrementingPollingCounter initial callback is asynchronous
      
      The initial measurement callback for `IncrementingPollingCounter` is now invoked asynchronously.
      
      ### InMemoryDirectoryInfo prepends rootDir to files
      
      `InMemoryDirectoryInfo` now prepends the `rootDir` to file paths, which may change how matching works.
      
      ### RuntimeHelpers.GetSubArray returns different type
      
      `RuntimeHelpers.GetSubArray` may return a different concrete array type than before.
      
      ### Support for empty environment variables
      
      Empty environment variables are now supported and no longer treated as unset.
      
      ### ZipArchiveEntry names and comments respect UTF8 flag
      
      `ZipArchiveEntry` now correctly uses UTF-8 encoding for names and comments when the UTF-8 flag is set in the entry header, which may change how non-ASCII entry names are read.
      
      ### Adding ZipArchiveEntry with CompressionLevel sets header flags
      
      Adding a `ZipArchiveEntry` with an explicit `CompressionLevel` now sets the general-purpose bit flags in the ZIP central directory header.
      
      ### Altered UnsafeAccessor support for non-open generics
      
      `UnsafeAccessor` behavior changed for non-open generic types.
      
      ### BinaryReader.ReadString() returns "\uFFFD" on malformed sequences
      
      `BinaryReader.ReadString()` now returns the Unicode replacement character instead of throwing for malformed byte sequences.
      
      ### Other behavioral changes (lower impact)
      
      - `ServicePointManager` (SYSLIB0014) is now fully obsolete — settings do not affect `SslStream` or `HttpClient`
      - `AuthenticationManager` (SYSLIB0009) methods now no-op or throw `PlatformNotSupportedException`
      
    • cryptography-dotnet8to9.md 2.1 KB
      # Cryptography Breaking Changes (.NET 9)
      
      These changes affect projects using `System.Security.Cryptography`, X.509 certificates, or OpenSSL.
      
      ## Source-Incompatible Changes
      
      ### X509Certificate2 and X509Certificate constructors are obsolete (SYSLIB0057)
      
      **Impact: High.** The constructors on `X509Certificate` and `X509Certificate2` that accept content as `byte[]`, `ReadOnlySpan<byte>`, or a file path are now obsolete. The `Import` methods on `X509Certificate2Collection` are also obsolete. Using them produces warning `SYSLIB0057`.
      
      ```csharp
      // BREAKS — SYSLIB0057 warning
      var cert = new X509Certificate2(certBytes);
      var cert2 = new X509Certificate2("cert.pfx", "password");
      collection.Import(certBytes);
      ```
      
      **Why:** These APIs accepted multiple formats (X.509, PKCS7, PKCS12/PFX) from a single parameter, making it possible to load a different format than intended with user-supplied data.
      
      **Fix:** Use `X509CertificateLoader` methods:
      ```csharp
      // Load DER/PEM encoded certificate
      var derCert = X509CertificateLoader.LoadCertificate(certBytes);
      
      // Load PFX/PKCS12
      var pfxCert = X509CertificateLoader.LoadPkcs12(pfxBytes, "password");
      
      // Load from file
      var fileCert = X509CertificateLoader.LoadCertificateFromFile("cert.pem");
      var filePfxCert = X509CertificateLoader.LoadPkcs12FromFile("cert.pfx", "password");
      ```
      
      ### APIs removed from System.Security.Cryptography.Pkcs netstandard2.0
      
      Some APIs were removed from the `netstandard2.0` target of the `System.Security.Cryptography.Pkcs` package. These APIs are still available when targeting .NET.
      
      ## Behavioral Changes
      
      ### SafeEvpPKeyHandle.DuplicateHandle up-refs the handle
      
      **Impact: Low.** `SafeEvpPKeyHandle.DuplicateHandle` now increments the reference count on the underlying OpenSSL key handle instead of creating a fully independent copy. This is more efficient but means changes to one handle may affect duplicates.
      
      ### Windows private key lifetime simplified
      
      **Impact: Low.** The lifetime management of private keys associated with certificates on Windows has been simplified. This may affect code that makes assumptions about when private key handles are released.
      
    • csharp-compiler-dotnet8to9.md 3.3 KB
      # C# 13 Compiler Breaking Changes (.NET 9)
      
      These breaking changes are introduced by the Roslyn compiler shipping with the .NET 9 SDK. They affect all projects targeting `net9.0` (which uses C# 13 by default). These are maintained separately from the runtime breaking changes at: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%209
      
      ## Source-Incompatible Changes
      
      ### InlineArray attribute on record structs is disallowed
      
      **Impact: Medium.** You can no longer apply the `[InlineArray]` attribute to `record struct` types. This produces error CS9259.
      
      ```csharp
      // BREAKS — CS9259 error
      [System.Runtime.CompilerServices.InlineArray(10)]
      record struct Buffer()
      {
          private int _element0;
      }
      ```
      
      **Fix:** Change `record struct` to a plain `struct`:
      ```csharp
      [System.Runtime.CompilerServices.InlineArray(10)]
      struct Buffer
      {
          private int _element0;
      }
      ```
      
      ### Iterators introduce safe context in C# 13
      
      **Impact: Low–Medium.** Although the language spec states that iterators introduce a safe context, Roslyn did not enforce this in C# 12 and lower. In C# 13, iterators always introduce a safe context, which can break scenarios where unsafe context was inherited by nested local functions.
      
      ```csharp
      // BREAKS in C# 13
      unsafe class C
      {
          System.Collections.Generic.IEnumerable<int> M()
          {
              yield return 1;
              local();
              void local()
              {
                  int* p = null; // error: unsafe code in safe context
              }
          }
      }
      ```
      
      **Fix:** Add the `unsafe` modifier to the local function:
      ```csharp
      unsafe void local()
      {
          int* p = null; // OK
      }
      ```
      
      ### Collection expression overload resolution changes
      
      **Impact: Low–Medium.** Two changes in how collection expressions resolve overloads:
      
      1. **Empty collection expressions no longer use span to tiebreak**: When passing `[]` to an overloaded method without a clear element type, `ReadOnlySpan<T>` vs `Span<T>` is no longer used to disambiguate. This can turn previously successful calls into errors.
      
      ```csharp
      class C
      {
          static void M(ReadOnlySpan<int> ros) {}
          static void M(Span<object> s) {}
      
          static void Main()
          {
              M([]); // Chose ReadOnlySpan<int> in C# 12, error in C# 13
          }
      }
      ```
      
      2. **Exact element type is now preferred**: Overload resolution prefers an exact element type match from expressions. This can change which overload is selected:
      
      ```csharp
      class C
      {
          static void M(ReadOnlySpan<byte> ros) {}
          static void M(Span<int> s) {}
      
          static void Main()
          {
              M([1]); // ReadOnlySpan<byte> in C# 12, Span<int> in C# 13
          }
      }
      ```
      
      **Fix:** Add explicit casts or use a typed variable to select the desired overload.
      
      ### Default and params parameters considered in method group natural type
      
      **Impact: Low.** The compiler previously inferred different delegate types depending on candidate order when default parameter values or `params` arrays were used. Now an ambiguity error is emitted. Fix by using explicit delegate types instead of `var`.
      
      ### Other low-impact source changes
      
      - **`scoped` in lambda parameters (inherited from C# 12 changes)**: Always treated as a modifier in newer language versions.
      - **Indexers without `DefaultMemberAttribute`**: No longer allowed (CS0656).
      - **`dotnet_style_require_accessibility_modifiers`** now enforces on interface members consistently.
      
    • deployment-runtime-dotnet8to9.md 2.4 KB
      # Deployment and Runtime Breaking Changes (.NET 9)
      
      These changes affect runtime configuration, deployment, and JIT compiler behavior.
      
      ## Deployment
      
      ### Environment variables take precedence in app runtime configuration settings
      
      **Impact: Medium.** When both an environment variable and a corresponding `runtimeconfig.json` setting are provided, the environment variable now takes precedence.
      
      Example `runtimeconfig.json`:
      ```json
      {
        "runtimeOptions": {
          "configProperties": {
            "System.GC.Server": true
          }
        }
      }
      ```
      
      Previously, this `runtimeconfig.json` setting would override `DOTNET_gcServer=0`. Now `DOTNET_gcServer=0` overrides the config file, disabling server GC.
      
      **Mitigation:** If your app runs in an environment with runtime configuration environment variables, either unset them or set them to the desired values. Ensure environment variables and config files are consistent.
      
      ### Deprecated desktop Windows/macOS/Linux MonoVM runtime packages
      
      **Impact: Low.** The desktop MonoVM runtime packages (`Microsoft.NETCore.App.Runtime.Mono.*`) are deprecated. Apps that explicitly used Mono on desktop should migrate to CoreCLR or use other supported runtimes.
      
      ## JIT Compiler
      
      ### Floating point to integer conversions are saturating
      
      **Impact: Medium.** On x86/x64, conversions from `float`/`double` to integer types now use **saturating** behavior instead of the previous platform-specific wrapping:
      
      | Scenario | .NET 8 (x86/x64) | .NET 9 |
      |----------|-------------------|--------|
      | `(int)float.MaxValue` | `int.MinValue` | `int.MaxValue` |
      | `(int)float.NaN` | `int.MinValue` | `0` |
      | `(uint)(-1.0f)` | Wrapping result | `0` |
      | `(ulong)(double.MaxValue)` | Wrapping result | `ulong.MaxValue` |
      
      ```csharp
      // .NET 8: (int)float.PositiveInfinity == int.MinValue  (wrapping)
      // .NET 9: (int)float.PositiveInfinity == int.MaxValue   (saturating)
      
      // .NET 8: (int)float.NaN == int.MinValue
      // .NET 9: (int)float.NaN == 0
      
      // .NET 8: (uint)(-1.0f) == some wrapping result
      // .NET 9: (uint)(-1.0f) == 0
      ```
      
      **Mitigation:** If you relied on the previous wrapping behavior (which was already non-deterministic across platforms), use the new `ConvertToIntegerNative<TInteger>` methods on `Single`, `Double`, and `Half` for the fast, platform-native behavior. Or use platform-specific hardware intrinsics for exact control.
      
      ### Some SVE APIs removed
      
      A small number of ARM SVE (Scalable Vector Extension) APIs were removed in RC2 that were previously available in previews.
      
    • efcore-dotnet8to9.md 6.9 KB
      # Entity Framework Core 9 Breaking Changes
      
      These changes affect projects using EF Core.
      
      ## High-Impact Changes
      
      ### Exception is thrown when applying migrations if there are pending model changes
      
      **Impact: High.** `dotnet ef database update`, `Migrate()`, and `MigrateAsync()` now throw if the model has pending changes compared to the last migration:
      
      > The model for context 'DbContext' has pending changes. Add a new migration before updating the database.
      
      **Common causes:**
      - No migrations exist at all (database managed through other means)
      - Model snapshot is missing (migration was created manually)
      - Non-deterministic model building (`DateTime.Now`, `Guid.NewGuid()` in `HasData`)
      - Last migration created for a different provider
      - ASP.NET Core Identity options that affect the model aren't applied in design-time factory
      
      **Non-deterministic `HasData` example (common pitfall):**
      ```csharp
      // BREAKS — DateTime.UtcNow changes every evaluation, causing "pending model changes"
      modelBuilder.Entity<Order>().HasData(
          new Order { Id = 1, CreatedAt = DateTime.UtcNow });
      
      // Fix — use a fixed constant
      modelBuilder.Entity<Order>().HasData(
          new Order { Id = 1, CreatedAt = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) });
      ```
      
      **Mitigation (temporary workaround — not recommended for production):**
      ```csharp
      // Suppress the warning if intentional — review before deploying to production,
      // as this risks silent schema drift between the model and the database.
      options.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
      ```
      
      ### Exception is thrown when applying migrations in an explicit transaction
      
      **Impact: High.** `Migrate()` and `MigrateAsync()` now manage their own transactions and `ExecutionStrategy`. Wrapping them in a user transaction throws:
      
      > A transaction was started before applying migrations. This prevents a database lock to be acquired.
      
      ```csharp
      // BREAKS
      await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () =>
      {
          await using var transaction = await dbContext.Database.BeginTransactionAsync(ct);
          await dbContext.Database.MigrateAsync(ct);
          await transaction.CommitAsync(ct);
      });
      
      // Fix — remove the external transaction
      await dbContext.Database.MigrateAsync(ct);
      ```
      
      **Mitigation (temporary workaround):** If you need the explicit transaction:
      ```csharp
      // Suppress if you understand the transaction safety implications.
      // EF Core manages its own transactions during migration; wrapping in a user
      // transaction can prevent proper lock acquisition.
      options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsUserTransactionWarning));
      ```
      
      ## Medium-Impact Changes
      
      ### `Microsoft.EntityFrameworkCore.Design` not found when using EF tools
      
      **Impact: Medium.** Starting with .NET SDK 9.0.200, the EF tools may fail with: `Could not load file or assembly 'Microsoft.EntityFrameworkCore.Design'`. This is caused by a change in how private assets are included in `.deps.json`.
      
      **Mitigation:** Mark the Design package as publishable:
      ```xml
      <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        <Publish>true</Publish>
      </PackageReference>
      ```
      
      ## Low-Impact Changes
      
      ### `EF.Functions.Unhex()` now returns `byte[]?`
      
      The return type annotation changed from `byte[]` to `byte[]?` to match SQLite's `unhex` function which returns NULL for invalid inputs. Add the null-forgiving operator (`!`) if you're certain the input is valid.
      
      ### Compiled models reference value converter methods directly
      
      Value converter methods referenced by compiled models must now be `public` or `internal` (not `private`), as the generated code references them directly for NativeAOT support.
      
      ### SqlFunctionExpression nullability arguments arity validated
      
      The number of arguments and nullability propagation arguments must now match. When in doubt, use `false` for the nullability argument.
      
      ### `ToString()` returns empty string for null instances
      
      `ToString()` on nullable value types in EF queries now consistently returns empty string when the value is `null`, matching C# behavior. Previously the result was inconsistent across types.
      
      ### Shared framework dependencies updated to 9.0.x
      
      EF Core 9.0 references 9.0.x versions of `System.Text.Json`, `Microsoft.Extensions.Caching.Memory`, etc. Apps targeting `net8.0` will deploy these assemblies separately instead of using the shared framework.
      
      ## Azure Cosmos DB Breaking Changes
      
      EF Core 9.0 has extensive Cosmos DB changes. If using the Cosmos DB provider:
      
      ### Discriminator property renamed to `$type` (High Impact)
      
      The default discriminator property name changed from `Discriminator` to `$type` to align with `System.Text.Json` conventions. Existing documents use the old name.
      
      **Mitigation:**
      ```csharp
      modelBuilder.Entity<Session>().HasDiscriminator<string>("Discriminator");
      ```
      
      ### `id` property no longer contains discriminator (High Impact)
      
      The `id` property now contains only the EF key property value (e.g., `123`), not `EntityType|KeyValue` (e.g., `Product|123`). Existing documents written by EF Core 8 still have the old compound format.
      
      **Implications for existing data:**
      - `FindAsync` and point-reads by id will fail because EF Core 9 generates `123` but the stored document has `Product|123`
      - New documents get the short format, creating id inconsistency with existing documents
      - Direct Cosmos SQL queries matching on `c.id` need updating
      
      **Mitigation:** Preserve the old id format so existing documents remain accessible:
      ```csharp
      modelBuilder.Entity<Product>().HasRootDiscriminatorInJsonId(true);
      ```
      
      ### JSON `id` property mapped to key (High Impact)
      
      The JSON `id` property is now mapped directly to the entity key property.
      
      ### Sync I/O no longer supported (Medium Impact)
      
      Synchronous methods like `SaveChanges()` and `ToList()` on the Cosmos DB provider now throw. Use async equivalents.
      
      ### SQL queries must project JSON values directly (Medium Impact)
      
      Raw SQL queries against the Cosmos DB provider must now project values from the JSON document directly using `VALUE`.
      
      ### Undefined results filtered from query results (Medium Impact)
      
      Query results that are `undefined` in Cosmos DB are now automatically filtered out.
      
      ### Incorrectly translated queries no longer translated (Medium Impact)
      
      Some queries that were previously translated incorrectly now throw `InvalidOperationException` to prevent silent data corruption.
      
      ### `HasIndex` now throws (Medium Impact)
      
      `HasIndex` on a Cosmos DB entity type now throws `InvalidOperationException` at startup instead of being silently ignored. Remove all `HasIndex` calls for Cosmos entities — Cosmos DB indexing is managed through the container's indexing policy (Azure Portal, Bicep, or Terraform), not through EF Core.
      
      ### `IncludeRootDiscriminatorInJsonId` renamed (Low Impact)
      
      Renamed to `HasRootDiscriminatorInJsonId` after RC2.
      
    • sdk-msbuild-dotnet8to9.md 2.3 KB
      # SDK and MSBuild Breaking Changes (.NET 9)
      
      These changes affect the .NET SDK, CLI tooling, NuGet, and MSBuild behavior.
      
      ## Source-Incompatible Changes
      
      ### Version requirements for .NET 9 SDK
      
      The .NET 9 SDK has updated minimum Visual Studio and MSBuild version requirements:
      
      - With VS 17.11, the .NET 9.0.100 SDK can only target `net8.0` and earlier frameworks
      - VS 17.12 or later is required to target `net9.0`
      
      Attempting to target `net9.0` in VS 17.11 produces: `NETSDK1223: Targeting .NET 9.0 or higher in Visual Studio 2022 17.11 is not supported.`
      
      ### Warning emitted for .NET Standard 1.x target
      
      Projects targeting .NET Standard 1.x now produce a build warning encouraging migration to a newer target.
      
      ### Warning emitted for .NET 7 target
      
      Projects targeting `net7.0` now produce a build warning because .NET 7 is out of support. Update to `net8.0` or `net9.0`.
      
      ### New default RID used when targeting .NET Framework
      
      A new default Runtime Identifier (RID) is used when targeting .NET Framework, which may affect build output paths.
      
      ## Behavioral Changes
      
      ### Terminal Logger is default
      
      **Impact: Medium for CI scripts.** `dotnet build` and other build-related CLI commands now use Terminal Logger by default for interactive terminal sessions. Terminal Logger formats output differently from the console logger.
      
      **Mitigation:**
      - Per-command: `--tl:off`
      - Global: set `MSBUILDTERMINALLOGGER=off` environment variable
      
      ### `dotnet workload` commands output change
      
      `dotnet workload` commands have changed their output format. Scripts that parse workload command output may need updating.
      
      ### `dotnet sln add` doesn't allow invalid file names
      
      `dotnet sln add` now validates file names more strictly.
      
      ### `dotnet watch` incompatible with Hot Reload for old frameworks
      
      `dotnet watch` in .NET 9 SDK is not compatible with Hot Reload when targeting older frameworks. Use the SDK matching the target framework for `dotnet watch` scenarios.
      
      ### `installer` repo version no longer documented
      
      The `installer` repository version is no longer included in `productcommits` information.
      
      ### MSBuild custom culture resource handling
      
      MSBuild custom culture resource handling has changed (also applies to .NET 9 SDK 9.0.200/9.0.300).
      
      ### Other behavioral changes
      
      - `--interactive` may have different defaults in some scenarios
      - Default workload management may behave differently
      
    • serialization-networking-dotnet8to9.md 5.4 KB
      # Serialization and Networking Breaking Changes (.NET 9)
      
      These changes affect projects using BinaryFormatter, System.Text.Json, HttpClient, and networking APIs.
      
      ## Serialization
      
      ### BinaryFormatter always throws
      
      **Impact: High.** The in-box `BinaryFormatter` implementation now always throws `NotSupportedException` on `Serialize` and `Deserialize`, even with settings that previously enabled its use. The `EnableUnsafeBinaryFormatterSerialization` AppContext switch has been removed.
      
      ```csharp
      // Serialization — BREAKS at runtime with NotSupportedException
      var formatter = new BinaryFormatter();
      formatter.Serialize(stream, obj); // throws
      ```
      
      ```csharp
      // Deserialization — BREAKS at runtime with NotSupportedException
      var formatter = new BinaryFormatter();
      var obj = formatter.Deserialize(stream); // throws
      ```
      
      **This is a major architectural decision.** If the project uses BinaryFormatter, the agent should stop and ask the user which replacement to use. Options include:
      1. **System.Text.Json** — Modern, high-performance, built-in
      2. **MessagePack** or **protobuf-net** — Compact binary formats
      3. **XmlSerializer** or **DataContractSerializer** — XML-based formats
      
      > **Security: Do not re-enable BinaryFormatter.** A NuGet compatibility package exists (`System.Runtime.Serialization.Formatters`) but it is unsupported and **inherently unsafe** — BinaryFormatter deserialization enables remote code execution from crafted payloads, which is why it was removed. Do not suggest or use this package.
      
      The choice depends on existing data format, backward compatibility requirements, and performance needs. Stored data may need a migration strategy.
      
      See the [BinaryFormatter migration guide](https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-migration-guide/) for detailed guidance.
      
      ### Nullable JsonDocument properties deserialize to JsonValueKind.Null
      
      **Impact: Medium.** Deserializing JSON `null` into `JsonDocument` now returns a non-null `JsonDocument` with `RootElement.ValueKind == JsonValueKind.Null` instead of returning C# `null`.
      
      ```csharp
      var doc = JsonSerializer.Deserialize<JsonDocument>("null");
      // .NET 8: doc is null
      // .NET 9: doc is not null, doc.RootElement.ValueKind == JsonValueKind.Null
      ```
      
      **Fix:** Update code that checks `doc is null` to also check `doc.RootElement.ValueKind`:
      ```csharp
      if (doc is null || doc.RootElement.ValueKind == JsonValueKind.Null)
      {
          // handle null
      }
      ```
      
      ### System.Text.Json metadata reader now unescapes metadata property names
      
      **Impact: Low.** The JSON metadata reader now unescapes metadata property names (like `$type`, `$id`). This may affect custom converters or code that processes raw JSON metadata.
      
      ## Networking
      
      ### HttpClientFactory uses SocketsHttpHandler as primary handler
      
      **Impact: Medium.** The default primary handler for `HttpClientFactory`-created clients is now `SocketsHttpHandler` instead of `HttpClientHandler` on platforms that support it. Code that casts the handler to `HttpClientHandler` will throw `InvalidCastException`.
      
      ```csharp
      // BREAKS — InvalidCastException at runtime in .NET 9
      services.AddHttpClient("test")
          .ConfigureHttpMessageHandlerBuilder(b =>
          {
              ((HttpClientHandler)b.PrimaryHandler).UseCookies = false; // throws
          });
      ```
      
      **Fix options:**
      ```csharp
      // Option 1: Explicitly configure a primary handler
      services.AddHttpClient("test")
          .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false });
      
      // Option 2: Check for both handler types
      services.AddHttpClient("test")
          .ConfigureHttpMessageHandlerBuilder(b =>
          {
              if (b.PrimaryHandler is HttpClientHandler hch) hch.UseCookies = false;
              else if (b.PrimaryHandler is SocketsHttpHandler shh) shh.UseCookies = false;
          });
      
      // Option 3: Set defaults for all clients
      services.ConfigureHttpClientDefaults(b =>
          b.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false }));
      ```
      
      `SocketsHttpHandler` also has `PooledConnectionLifetime` preset to match `HandlerLifetime`, improving DNS rotation for captured clients.
      
      ### HttpClientFactory logging redacts header values by default
      
      **Impact: Medium.** All header values in `Trace`-level `HttpClientFactory` logs are now redacted by default. Previously, unspecified headers were logged in full.
      
      **Fix:** Explicitly allowlist specific non-sensitive headers that need to be logged:
      ```csharp
      // Allow specific non-sensitive headers to be logged unredacted
      services.ConfigureHttpClientDefaults(b =>
          b.RedactLoggedHeaders(h => h != "Cache-Control" && h != "Accept"));
      ```
      
      > **Warning:** Do not disable redaction globally with `RedactLoggedHeaders(_ => false)`. This logs all header values in cleartext, including `Authorization` tokens, cookies, and other credentials. Logs are often broadly accessible or exported to external systems, making this a credential leak risk.
      
      ### HttpClient metrics report `server.port` unconditionally
      
      `HttpClient` metrics now always include the `server.port` attribute, even for default ports (80/443). This may affect metric dashboards or alerting rules.
      
      ### HttpListenerRequest.UserAgent is nullable
      
      `HttpListenerRequest.UserAgent` is now `string?` instead of `string`. Add null checks.
      
      ### URI query redaction in HttpClient EventSource events
      
      URI query strings are now redacted in `HttpClient` EventSource events for security.
      
      ### URI query redaction in IHttpClientFactory logs
      
      URI query strings are now redacted in `IHttpClientFactory` logs for security.
      
    • winforms-wpf-dotnet8to9.md 3.7 KB
      # Windows Forms and WPF Breaking Changes (.NET 9)
      
      These changes affect projects using Windows Forms (`<UseWindowsForms>true</UseWindowsForms>`) or WPF (`<UseWPF>true</UseWPF>`).
      
      ## Windows Forms
      
      ### Source-Incompatible Changes
      
      #### Changes to nullability annotations
      
      **Impact: Medium.** Some WinForms API parameters changed nullability. Specifically, `IWindowsFormsEditorService.DropDownControl(Control)` parameter was previously nullable and is now non-nullable. Update implementations and call sites to match.
      
      #### New security analyzers (WFO1000)
      
      **Impact: Medium.** New analyzers enforce that properties in controls and `UserControl` objects have explicit serialization configuration via `DesignerSerializationVisibilityAttribute`, `DefaultValueAttribute`, or `ShouldSerialize[PropertyName]` methods. By default, the analyzer produces an **error**.
      
      ```
      WFO1000: Property 'property' does not configure the code serialization for its property content.
      ```
      
      **Fix:** Add appropriate serialization attributes to flagged properties — `DesignerSerializationVisibilityAttribute`, `DefaultValueAttribute`, or a `ShouldSerialize[PropertyName]` method.
      
      > **Warning:** Do not suppress WFO1000 globally. This analyzer guards against insecure deserialization of control properties in the WinForms designer. Suppressing it can leave controls vulnerable to deserialization attacks through crafted designer files. If specific properties are intentionally excluded from serialization, use `[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]` on those properties rather than silencing the analyzer project-wide.
      
      ### Behavioral Changes
      
      #### StatusStrip uses a different default renderer
      
      **Impact: Low.** `StatusStrip.RenderMode` no longer defaults to `ToolStripRenderMode.System`. The visual appearance may differ. Set `RenderMode` explicitly to restore the previous look:
      ```csharp
      statusStrip.RenderMode = ToolStripRenderMode.System;
      ```
      
      > Note: This change was reverted in a .NET 9 servicing release.
      
      #### PictureBox raises HttpClient exceptions
      
      **Impact: Low.** `PictureBox` now raises `HttpRequestException` and `TaskCanceledException` instead of `WebException` when loading images from URLs fails. Update catch blocks:
      ```csharp
      // Before
      try { pictureBox.Load(url); }
      catch (WebException) { }
      
      // After
      try { pictureBox.Load(url); }
      catch (HttpRequestException) { }
      catch (TaskCanceledException) { }
      ```
      
      #### IMsoComponent support is opt-in
      
      **Impact: Low.** WinForms threads no longer automatically register with `IMsoComponentManager` instances. To restore:
      ```xml
      <ItemGroup>
        <RuntimeHostConfigurationOption Include="Switch.System.Windows.Forms.EnableMsoComponentManager" Value="true" />
      </ItemGroup>
      ```
      
      #### BindingSource.SortDescriptions doesn't return null
      
      `SortDescriptions` now returns an empty collection instead of null.
      
      #### ComponentDesigner.Initialize throws ArgumentNullException
      
      `ComponentDesigner.Initialize` now throws `ArgumentNullException` for null input.
      
      #### DataGridViewRowAccessibleObject.Name starting row index
      
      The starting row index in accessible object names has changed.
      
      #### No exception if DataGridView is null
      
      `DataGridViewHeaderCell` no longer throws `NullReferenceException` when `DataGridView` is null.
      
      ## WPF
      
      ### Behavioral Changes / Source-Incompatible
      
      #### `XmlNamespaceMaps` type change
      
      **Impact: Low.** The backing property of `XmlAttributeProperties.XmlNamespaceMaps` changed from `String` to `Hashtable`. The `SetXmlNamespaceMaps` method now accepts `Hashtable` instead of `String`.
      
      ```csharp
      // Before — passed string
      XmlAttributeProperties.SetXmlNamespaceMaps(obj, someString);
      
      // After — pass Hashtable
      XmlAttributeProperties.SetXmlNamespaceMaps(obj, someHashtable);
      ```
      
      `GetXmlNamespaceMaps` now returns `Hashtable` instead of `String`.
      
  • SKILL.md 15.4 KB
    ---
    name: migrate-dotnet8-to-dotnet9
    description: >
      Migrate a .NET 8 project to .NET 9 and resolve all breaking changes.
      USE FOR: upgrading TargetFramework from net8.0 to net9.0, fixing build errors
      after updating the .NET 9 SDK, resolving behavioral changes in .NET 9 / C# 13 /
      ASP.NET Core 9 / EF Core 9, replacing BinaryFormatter (now always throws),
      resolving SYSLIB0054-SYSLIB0057, adapting to params span overload resolution,
      fixing C# 13 compiler changes, updating HttpClientFactory for SocketsHttpHandler,
      and resolving EF Core 9 migration/Cosmos DB changes.
      DO NOT USE FOR: .NET Framework migrations, upgrading from .NET 7 or earlier,
      greenfield .NET 9 projects, or cosmetic modernization unrelated to the upgrade.
    license: MIT
    ---
    
    # .NET 8 → .NET 9 Migration
    
    Migrate a .NET 8 project or solution to .NET 9, systematically resolving all breaking changes. The outcome is a project targeting `net9.0` that builds cleanly, passes tests, and accounts for every behavioral, source-incompatible, and binary-incompatible change introduced in the .NET 9 release.
    
    ## When to Use
    
    - Upgrading `TargetFramework` from `net8.0` to `net9.0`
    - Resolving build errors or new warnings after updating the .NET 9 SDK
    - Adapting to behavioral changes in .NET 9 runtime, ASP.NET Core 9, or EF Core 9
    - Replacing `BinaryFormatter` usage (now always throws at runtime)
    - Updating CI/CD pipelines, Dockerfiles, or deployment scripts for .NET 9
    
    ## When Not to Use
    
    - The project already targets `net9.0` and builds cleanly — migration is done. If the goal is to reach `net10.0`, use the `migrate-dotnet9-to-dotnet10` skill as the next step.
    - Upgrading from .NET 7 or earlier — address the prior version breaking changes first
    - Migrating from .NET Framework — that is a separate, larger effort
    - Greenfield projects that start on .NET 9 (no migration needed)
    
    ## Inputs
    
    | Input | Required | Description |
    |-------|----------|-------------|
    | Project or solution path | Yes | The `.csproj`, `.sln`, or `.slnx` entry point to migrate |
    | Build command | No | How to build (e.g., `dotnet build`, a repo build script). Auto-detect if not provided |
    | Test command | No | How to run tests (e.g., `dotnet test`). Auto-detect if not provided |
    | Project type hints | No | Whether the project uses ASP.NET Core, EF Core, WinForms, WPF, containers, etc. Auto-detect from PackageReferences and SDK attributes if not provided |
    
    ## Workflow
    
    > **Answer directly from the loaded reference documents.** Do not search the filesystem or fetch web pages for breaking change information — the references contain the authoritative details. Focus on identifying which breaking changes apply and providing concrete fixes.
    >
    > **Commit strategy:** Commit at each logical boundary — after updating the TFM (Step 2), after resolving build errors (Step 3), after addressing behavioral changes (Step 4), and after updating infrastructure (Step 5). This keeps each commit focused and reviewable.
    
    ### Step 1: Assess the project
    
    1. Identify how the project is built and tested. Look for build scripts, `.sln`/`.slnx` files, or individual `.csproj` files.
    2. Run `dotnet --version` to confirm the .NET 9 SDK is installed. If it is not, stop and inform the user.
    3. Determine which technology areas the project uses by examining:
       - **SDK attribute**: `Microsoft.NET.Sdk.Web` → ASP.NET Core; `Microsoft.NET.Sdk.WindowsDesktop` with `<UseWPF>` or `<UseWindowsForms>` → WPF/WinForms
       - **PackageReferences**: `Microsoft.EntityFrameworkCore.*` → EF Core; `Microsoft.Extensions.Http` → HttpClientFactory
       - **Dockerfile presence** → Container changes relevant
       - **P/Invoke or native interop usage** → Interop changes relevant
       - **`BinaryFormatter` usage** → Serialization migration needed
       - **`System.Text.Json` usage** → Serialization changes relevant
       - **X509Certificate constructors** → Cryptography changes relevant
    4. Record which reference documents are relevant (see the reference loading table in Step 3).
    5. Do a **clean build** (`dotnet build --no-incremental` or delete `bin`/`obj`) on the current `net8.0` target to establish a clean baseline. Record any pre-existing warnings.
    
    ### Step 2: Update the Target Framework
    
    1. In each `.csproj` (or `Directory.Build.props` if centralized), change:
       ```xml
       <TargetFramework>net8.0</TargetFramework>
       ```
       to:
       ```xml
       <TargetFramework>net9.0</TargetFramework>
       ```
       For multi-targeted projects, add `net9.0` to `<TargetFrameworks>` or replace `net8.0`.
    
    2. Update all `Microsoft.Extensions.*`, `Microsoft.AspNetCore.*`, `Microsoft.EntityFrameworkCore.*`, and other Microsoft package references to their 9.0.x versions. If using Central Package Management (`Directory.Packages.props`), update versions there.
    
    3. Run `dotnet restore`. Watch for:
       - **Version requirements**: .NET 9 SDK requires Visual Studio 17.12+ to target `net9.0` (17.11 for `net8.0` and earlier).
       - **New warnings for .NET Standard 1.x** and **.NET 7 targets** — consider updating or removing outdated target frameworks.
    
    4. Run a clean build. Collect all errors and new warnings. These will be addressed in Step 3.
    
    ### Step 3: Resolve build errors and source-incompatible changes
    
    Work through compilation errors and new warnings systematically. Load the appropriate reference documents based on the project type:
    
    | If the project uses… | Load reference |
    |-----------------------|----------------|
    | Any .NET 9 project | `references/csharp-compiler-dotnet8to9.md` |
    | Any .NET 9 project | `references/core-libraries-dotnet8to9.md` |
    | Any .NET 9 project | `references/sdk-msbuild-dotnet8to9.md` |
    | ASP.NET Core | `references/aspnet-core-dotnet8to9.md` |
    | Entity Framework Core | `references/efcore-dotnet8to9.md` |
    | Cryptography APIs | `references/cryptography-dotnet8to9.md` |
    | System.Text.Json, HttpClient, networking | `references/serialization-networking-dotnet8to9.md` |
    | Windows Forms or WPF | `references/winforms-wpf-dotnet8to9.md` |
    | Docker containers, native interop | `references/containers-interop-dotnet8to9.md` |
    | Runtime configuration, deployment | `references/deployment-runtime-dotnet8to9.md` |
    
    **Common source-incompatible changes to check for:**
    
    1. **`params` span overload resolution** — New `params ReadOnlySpan<T>` overloads on `String.Join`, `String.Concat`, `Path.Combine`, `Task.WhenAll`, and many more now bind preferentially. Code calling these methods inside `Expression` lambdas will fail (CS8640/CS9226). See `references/core-libraries-dotnet8to9.md`.
    
    2. **`StringValues` ambiguous overload** — The `params Span<T>` feature creates ambiguity with `StringValues` implicit operators on methods like `String.Concat`, `String.Join`, `Path.Combine`. Fix by explicitly casting arguments. See `references/core-libraries-dotnet8to9.md`.
    
    3. **New obsoletion warnings (SYSLIB0054–SYSLIB0057)**:
       - `SYSLIB0054`: Replace `Thread.VolatileRead`/`VolatileWrite` with `Volatile.Read`/`Volatile.Write`
       - `SYSLIB0057`: Replace `X509Certificate2`/`X509Certificate` binary/file constructors with `X509CertificateLoader` methods
       - Also `SYSLIB0055` (ARM AdvSimd signed overloads) and `SYSLIB0056` (Assembly.LoadFrom with hash algorithm) — see `references/core-libraries-dotnet8to9.md`
    
    4. **C# 13 `InlineArray` on record structs** — `[InlineArray]` attribute on `record struct` types is now disallowed (CS9259). Change to a regular `struct`. See `references/csharp-compiler-dotnet8to9.md`.
    
    5. **C# 13 iterator safe context** — Iterators now introduce a safe context in C# 13. Local functions inside iterators that used unsafe code inherited from an outer `unsafe` class will now error. Add `unsafe` modifier to the local function. See `references/csharp-compiler-dotnet8to9.md`.
    
    6. **C# 13 collection expression overload resolution** — Empty collection expressions (`[]`) no longer use span vs non-span to tiebreak overloads. Exact element type is now preferred. See `references/csharp-compiler-dotnet8to9.md`.
    
    7. **`String.Trim(params ReadOnlySpan<char>)` removed** — Code compiled against .NET 9 previews that passes `ReadOnlySpan<char>` to `Trim`/`TrimStart`/`TrimEnd` must rebuild; the overload was removed in GA. See `references/core-libraries-dotnet8to9.md`.
    
    8. **`BinaryFormatter` always throws** — If the project uses `BinaryFormatter`, **stop and inform the user** — this is a major decision. See `references/serialization-networking-dotnet8to9.md`.
    
    9. **`HttpListenerRequest.UserAgent` is nullable** — The property is now `string?`. Add null checks. See `references/serialization-networking-dotnet8to9.md`.
    
    10. **Windows Forms nullability annotation changes** — Some WinForms API parameters changed from nullable to non-nullable. Update call sites. See `references/winforms-wpf-dotnet8to9.md`.
    
    11. **Windows Forms security analyzers (WFO1000)** — New analyzers produce errors for properties without explicit serialization configuration. See `references/winforms-wpf-dotnet8to9.md`.
    
    Build again after each batch of fixes. Repeat until the build is clean.
    
    ### Step 4: Address behavioral changes
    
    Behavioral changes do not cause build errors but may change runtime behavior. Review each applicable item and determine whether the previous behavior was relied upon.
    
    **High-impact behavioral changes (check first):**
    
    1. **Floating-point to integer conversions are now saturating** — Conversions from `float`/`double` to integer types now saturate instead of wrapping on x86/x64. See `references/deployment-runtime-dotnet8to9.md`.
    
    2. **EF Core: Pending model changes exception** — `Migrate()`/`MigrateAsync()` now throws if the model has pending changes. **Search for `DateTime.Now`, `DateTime.UtcNow`, or `Guid.NewGuid()` in any `HasData` call — these must be replaced with fixed constants** (e.g., `new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc)`). See `references/efcore-dotnet8to9.md`.
    
    3. **EF Core: Explicit transaction exception** — `Migrate()` inside a user transaction now throws. See `references/efcore-dotnet8to9.md`.
    
    4. **HttpClientFactory uses `SocketsHttpHandler` by default** — Code that casts the primary handler to `HttpClientHandler` will get `InvalidCastException`. See `references/serialization-networking-dotnet8to9.md`.
    
    5. **HttpClientFactory header redaction by default** — All header values in `Trace`-level logs are now redacted. See `references/serialization-networking-dotnet8to9.md`.
    
    6. **Environment variables take precedence over runtimeconfig.json** — Runtime configuration settings from environment variables now override `runtimeconfig.json`. See `references/deployment-runtime-dotnet8to9.md`.
    
    7. **ASP.NET Core `ValidateOnBuild`/`ValidateScopes` in development** — `HostBuilder` now enables DI validation in development by default. See `references/aspnet-core-dotnet8to9.md`.
    
    **Other behavioral changes to review (may cause runtime exceptions ⚠️ or subtle behavioral differences):**
    
    - ⚠️ `FromKeyedServicesAttribute` no longer injects non-keyed service fallback — throws `InvalidOperationException`
    - ⚠️ Container images no longer install zlib — apps depending on system zlib will fail
    - ⚠️ Intel CET is now enabled by default — non-CET-compatible native libraries may cause process termination
    - `BigInteger` now has a maximum length of `(2^31) - 1` bits
    - `JsonDocument` deserialization of JSON `null` now returns non-null `JsonDocument` with `JsonValueKind.Null` instead of C# `null`
    - `System.Text.Json` metadata reader now unescapes metadata property names
    - `ZipArchiveEntry` names/comments now respect the UTF-8 flag
    - `IncrementingPollingCounter` initial callback is now asynchronous
    - `InMemoryDirectoryInfo` prepends rootDir to files
    - `RuntimeHelpers.GetSubArray` returns a different type
    - `PictureBox` raises `HttpRequestException` instead of `WebException`
    - `StatusStrip` uses a different default renderer
    - `IMsoComponent` support is opt-in
    - `SafeEvpPKeyHandle.DuplicateHandle` up-refs the handle
    - `HttpClient` metrics report `server.port` unconditionally
    - URI query strings redacted in HttpClient EventSource events and IHttpClientFactory logs
    - `dotnet watch` is incompatible with Hot Reload for old frameworks
    - WPF `GetXmlNamespaceMaps` returns `Hashtable` instead of `String`
    
    ### Step 5: Update infrastructure
    
    1. **Dockerfiles**: Update base images. Note that .NET 9 container images no longer install zlib. If your app depends on zlib, add `RUN apt-get update && apt-get install -y zlib1g` to your Dockerfile.
       ```dockerfile
       # Before
       FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
       FROM mcr.microsoft.com/dotnet/aspnet:8.0
       # After
       FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
       FROM mcr.microsoft.com/dotnet/aspnet:9.0
       ```
    
    2. **CI/CD pipelines**: Update SDK version references. If using `global.json`, update:
       ```json
       {
         "sdk": {
           "version": "9.0.100",
           "rollForward": "latestFeature"
         }
       }
       ```
       Review the `rollForward` policy — if set to `"disable"` or `"latestPatch"`, the SDK may not resolve correctly after upgrading. `"latestFeature"` (recommended) allows the SDK to roll forward to the latest 9.0.x feature band.
    
    3. **Visual Studio version**: .NET 9 SDK requires VS 17.12+ to target `net9.0`. VS 17.11 can only target `net8.0` and earlier.
    
    4. **Terminal Logger**: `dotnet build` now uses Terminal Logger by default in interactive terminals. CI scripts that parse MSBuild console output may need `--tl:off` or `MSBUILDTERMINALLOGGER=off`.
    
    5. **`dotnet workload` output**: Output format has changed. Update any scripts that parse workload command output.
    
    6. **.NET Monitor images**: Tags simplified to version-only (affects container orchestration referencing specific tags).
    
    ### Step 6: Verify
    
    1. Run a full clean build: `dotnet build --no-incremental`
    2. Run all tests: `dotnet test`
    3. If the application is containerized, build and test the container image
    4. Smoke-test the application, paying special attention to:
       - BinaryFormatter usage (will throw at runtime)
       - Floating-point to integer conversion behavior
       - EF Core migration application
       - HttpClientFactory handler casting and logging
       - DI validation in development environment
       - Runtime configuration settings (environment variable precedence)
    5. Review the diff and ensure no unintended behavioral changes were introduced
    
    ## Reference Documents
    
    The `references/` folder contains detailed breaking change information organized by technology area. Load only the references relevant to the project being migrated:
    
    | Reference file | When to load |
    |----------------|-------------|
    | `references/csharp-compiler-dotnet8to9.md` | Always (C# 13 compiler breaking changes — InlineArray on records, iterator safe context, collection expression overloads) |
    | `references/core-libraries-dotnet8to9.md` | Always (applies to all .NET 9 projects) |
    | `references/sdk-msbuild-dotnet8to9.md` | Always (SDK and build tooling changes) |
    | `references/aspnet-core-dotnet8to9.md` | Project uses ASP.NET Core |
    | `references/efcore-dotnet8to9.md` | Project uses Entity Framework Core |
    | `references/cryptography-dotnet8to9.md` | Project uses System.Security.Cryptography or X.509 certificates |
    | `references/serialization-networking-dotnet8to9.md` | Project uses BinaryFormatter, System.Text.Json, HttpClient, or networking APIs |
    | `references/winforms-wpf-dotnet8to9.md` | Project uses Windows Forms or WPF |
    | `references/containers-interop-dotnet8to9.md` | Project uses Docker containers or native interop (P/Invoke) |
    | `references/deployment-runtime-dotnet8to9.md` | Project uses runtime configuration, deployment, or has floating-point to integer conversions |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related