Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
75a3f1f
Add migrate-dotnet8-to-dotnet9 skill with references and eval scenarios
danmoseley Feb 28, 2026
cd1ddcd
BinaryFormatter: stop and ask user for replacement strategy
danmoseley Feb 28, 2026
37156ab
Trim SKILL.md to delegate BinaryFormatter detail to reference
danmoseley Feb 28, 2026
0d91cc7
Trim SKILL.md: delegate fix details to references, remove BinaryForma…
danmoseley Feb 28, 2026
751f359
Improve eval scenarios: setup files, task-oriented prompts, expect_ac…
danmoseley Feb 28, 2026
40cd88c
Refocus eval scenarios on Opus blind spots
danmoseley Feb 28, 2026
3ebbf5d
Add forward-chaining guidance to migrate-dotnet8-to-dotnet9 skill
danmoseley Mar 3, 2026
f7fc24b
Fix EF Core eval scenarios to start with 8.0.0 package versions
danmoseley Mar 3, 2026
cd6deb9
Fix InlineArray eval to actually exceed 1 MiB size limit
danmoseley Mar 3, 2026
706df23
Fix non-compiling ConfigurePrimaryHttpMessageHandler in eval
danmoseley Mar 3, 2026
de73efc
Fix non-compiling HttpClientFactory example in reference doc
danmoseley Mar 3, 2026
e4df9ce
Remove unsafe KnownProxies.Clear() guidance from aspnet-core reference
danmoseley Mar 3, 2026
a5e14b9
Remove unsafe RedactLoggedHeaders guidance from reference doc
danmoseley Mar 3, 2026
38e79b4
Remove insecure BinaryFormatter re-enablement option from guidance
danmoseley Mar 3, 2026
380f0de
Add security warning against suppressing WFO1000 analyzer
danmoseley Mar 3, 2026
87b00dd
Add security warning to CET opt-out guidance
danmoseley Mar 3, 2026
8112602
Label EF Core warning suppressions as temporary workarounds
danmoseley Mar 3, 2026
3621052
Prioritize behavioral changes and add global.json rollForward guidance
danmoseley Mar 3, 2026
397190d
Split BinaryFormatter code snippet into separate serialize/deserializ…
danmoseley Mar 4, 2026
b873e95
Fix SYSLIB heading to include full range SYSLIB0054-SYSLIB0057
danmoseley Mar 4, 2026
222c0e6
Fix eval rubric to match setup code: Migrate/MigrateAsync
danmoseley Mar 4, 2026
d766e14
Use distinct variable names in X509CertificateLoader examples
danmoseley Mar 4, 2026
4c9f5eb
Remove misleading 'backported from .NET 8' from ForwardedHeaders section
danmoseley Mar 4, 2026
bb6208e
Clarify GetXmlNamespaceMaps InvalidCastException explanation
danmoseley Mar 4, 2026
97f9872
Update plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/references/ef…
danmoseley Mar 5, 2026
2ebd897
Fix ReadOnlySpan<char> String.Trim example and eval scenario
danmoseley Mar 5, 2026
a87e07c
Merge branch 'main' into migrate-dotnet8-to-dotnet9
danmoseley Mar 5, 2026
8e3a007
Add ViktorHofer as co-owner for migrate-dotnet8-to-dotnet9 skill
danmoseley Mar 5, 2026
4d7016e
Add -dotnet8to9 suffix to reference filenames to avoid cross-skill co…
danmoseley Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
/plugins/dotnet/skills/microbenchmarking/ @dotnet/dotnet-perf-team
/tests/dotnet/microbenchmarking/ @dotnet/dotnet-perf-team

/plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/ @danmoseley @ViktorHofer
/tests/dotnet/migrate-dotnet8-to-dotnet9/ @danmoseley @ViktorHofer

/plugins/dotnet/skills/dotnet-aot-compat/ @agocke @dotnet/appmodel
/tests/dotnet/dotnet-aot-compat/ @agocke @dotnet/appmodel

Expand Down
240 changes: 240 additions & 0 deletions plugins/dotnet/skills/migrate-dotnet8-to-dotnet9/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# 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.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`
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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.
Loading