-
Notifications
You must be signed in to change notification settings - Fork 24.6k
Document ASP.NET Core breaking changes in .NET 11 #37228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e587498
Port .NET 11 ASP.NET Core breaking changes from dotnet/docs#54188
halter73 00bd528
[WIP] [WIP] Address feedback on metadata placement in ASP.NET Core br…
Copilot 9f274f3
Apply suggestions from code review
guardrex 00de9b9
Note cross-version/server scope of HTTP/1.1 framing fix
halter73 f011a27
Document multiple ProducesResponseType per status code change
halter73 c2d00ee
Update aspnetcore/breaking-changes/11/openapi-multiple-produces-per-s…
halter73 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
68 changes: 68 additions & 0 deletions
68
aspnetcore/breaking-changes/11/blazor-custom-event-name-collision.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| --- | ||
| title: "Breaking change: Blazor custom event registration throws when name matches a browser event" | ||
| description: "Learn about the breaking change in ASP.NET Core 11 where Blazor.registerCustomEventType throws when eventName equals browserEventName." | ||
| ms.date: 06/04/2026 | ||
| ai-usage: ai-assisted | ||
| --- | ||
|
|
||
|
guardrex marked this conversation as resolved.
Outdated
|
||
| # Blazor custom event registration throws when name matches a browser event | ||
|
|
||
| The Blazor JavaScript API `Blazor.registerCustomEventType` now throws an error when the custom event name matches its `browserEventName` option. Registering a custom event with the same name as the underlying browser event caused the event to fire twice for each user action. | ||
|
|
||
| ## Version introduced | ||
|
|
||
| .NET 11 | ||
|
|
||
| ## Previous behavior | ||
|
|
||
| Previously, you could call `Blazor.registerCustomEventType` with an `eventName` equal to the `browserEventName` option. The call succeeded silently, but every browser event of that name caused two `EventCallback` invocations on the .NET side: one for the native browser event and one for the custom event wrapper that re-fired the same name. | ||
|
|
||
| ```javascript | ||
| // This used to silently double-fire the event. | ||
| Blazor.registerCustomEventType('scrolltop', { | ||
| browserEventName: 'scrolltop' | ||
| }); | ||
| ``` | ||
|
|
||
| ## New behavior | ||
|
|
||
| Starting in ASP.NET Core 11, `Blazor.registerCustomEventType` throws when `eventName` equals `options.browserEventName`: | ||
|
|
||
| ```javascript | ||
| // This now throws synchronously. | ||
| Blazor.registerCustomEventType('scrolltop', { | ||
| browserEventName: 'scrolltop' | ||
| }); | ||
| ``` | ||
|
|
||
| To wrap a browser event in a custom event without the collision, give the custom event a different name: | ||
|
|
||
| ```javascript | ||
| Blazor.registerCustomEventType('customscroll', { | ||
| browserEventName: 'scrolltop' | ||
| }); | ||
| ``` | ||
|
|
||
| ## Type of breaking change | ||
|
|
||
| This change is a [behavioral change](/dotnet/core/compatibility/categories#behavioral-change). | ||
|
|
||
| ## Reason for change | ||
|
|
||
| Calling `Blazor.registerCustomEventType` with the same name for the custom and browser events was almost always a mistake that produced silently incorrect double-firing. The new validation surfaces the mistake at registration time. For more information, see [dotnet/aspnetcore#64774](https://github.com/dotnet/aspnetcore/pull/64774). | ||
|
|
||
| ## Recommended action | ||
|
|
||
| If your code calls `Blazor.registerCustomEventType` with a custom name that's the same as the underlying browser event, rename the custom event to anything else (a common convention is to add a prefix such as `custom` or your component-library name): | ||
|
|
||
| ```javascript | ||
| Blazor.registerCustomEventType('lib-scroll', { | ||
| browserEventName: 'scrolltop' | ||
| }); | ||
| ``` | ||
|
|
||
| Then update the corresponding `[EventHandler]` attribute or `@on*` directive on the .NET side to use the new name. | ||
|
|
||
| ## Affected APIs | ||
|
|
||
| None. `Blazor.registerCustomEventType` is a JavaScript API; it isn't surfaced through the .NET object model. For more information, see [custom events in Blazor](/aspnet/core/blazor/components/event-handling#custom-event-arguments). | ||
50 changes: 50 additions & 0 deletions
50
aspnetcore/breaking-changes/11/blazor-enhanced-nav-preloading-disabled.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| --- | ||
| title: "Breaking change: Blazor enhanced navigation no longer preloads resources" | ||
| description: "Learn about the breaking change in ASP.NET Core 11 where Blazor's ResourcePreloader no longer emits preload link hints for pages reached through enhanced navigation." | ||
| ms.date: 06/04/2026 | ||
| ai-usage: ai-assisted | ||
| --- | ||
|
|
||
|
guardrex marked this conversation as resolved.
Outdated
|
||
| # Blazor enhanced navigation no longer preloads resources | ||
|
|
||
| Blazor no longer emits `<link rel="modulepreload">` or `<link rel="preload">` resource hints for pages reached through enhanced navigation. Resource preloading still works for the initial page load and for normal (non-enhanced) navigation. | ||
|
|
||
| ## Version introduced | ||
|
|
||
| .NET 11 | ||
|
|
||
| ## Previous behavior | ||
|
|
||
| Previously, Blazor's `ResourcePreloader` emitted preload `<link>` elements for resources used by the destination page on every server-rendered request, including requests served as an enhanced navigation. | ||
|
|
||
| When an enhanced navigation occurred, the new preload elements were merged into the existing DOM. The merge often added and removed `<link>` elements in a different order from the prior page, which caused some browsers to issue more preload requests than needed. The Blazor WebAssembly runtime also doesn't restart on an enhanced navigation, so preload hints emitted for it after the first page were redundant. | ||
|
|
||
| ## New behavior | ||
|
|
||
| Starting in ASP.NET Core 11, `ResourcePreloader` only emits preload elements when the response isn't an enhanced navigation. Pages reached by enhanced navigation no longer receive the implicit preload hints that the framework added before. | ||
|
|
||
| The previous behavior on the initial page load (the first request, before any enhanced navigation occurs) is unchanged. Pages that the user navigates to without enhanced navigation (for example, full-page reloads or external navigations) also still receive preload hints. | ||
|
|
||
| ## Type of breaking change | ||
|
|
||
| This change is a [behavioral change](/dotnet/core/compatibility/categories#behavioral-change). | ||
|
|
||
| ## Reason for change | ||
|
|
||
| The implicit preload hints during enhanced navigation produced redundant browser preload requests because the WebAssembly runtime doesn't restart and the DOM-merge step often produced an unstable order of `<link>` elements. Removing the hints for enhanced navigation gives more predictable network behavior. For more information, see [dotnet/aspnetcore#63544](https://github.com/dotnet/aspnetcore/pull/63544). | ||
|
|
||
| ## Recommended action | ||
|
|
||
| If you relied on the implicit preload hints during enhanced navigation, add explicit `<link rel="preload">` or `<link rel="modulepreload">` tags in your `<HeadContent>` for the specific resources you want to preload: | ||
|
|
||
| ```razor | ||
| <HeadContent> | ||
| <link rel="modulepreload" href="_content/MyLib/big-module.js" /> | ||
| </HeadContent> | ||
| ``` | ||
|
|
||
| Alternatively, opt the relevant links out of enhanced navigation if preloading is more important to you than the snappier in-place navigation. For more information, see [enhanced navigation and form handling](/aspnet/core/blazor/fundamentals/routing#enhanced-navigation-and-form-handling). | ||
|
|
||
| ## Affected APIs | ||
|
|
||
| None. No public API surface changed. The change affects the implicit resource preloading performed by Blazor server-side rendering. | ||
89 changes: 89 additions & 0 deletions
89
aspnetcore/breaking-changes/11/blazor-obsolete-apis-removed.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| --- | ||
| title: "Breaking change: Obsolete Blazor APIs removed" | ||
| description: "Learn about the breaking change in ASP.NET Core 11 where APIs that were marked obsolete in earlier Blazor releases have been removed." | ||
| ms.date: 06/04/2026 | ||
| ai-usage: ai-assisted | ||
| --- | ||
|
|
||
|
guardrex marked this conversation as resolved.
Outdated
|
||
| # Obsolete Blazor APIs removed | ||
|
|
||
| A set of APIs across the Blazor Components assemblies that were previously marked obsolete have been removed in ASP.NET Core 11. Most of these APIs were marked obsolete several releases ago and have working replacements that have been recommended for years. | ||
|
|
||
| ## Version introduced | ||
|
|
||
| .NET 11 | ||
|
|
||
| ## Previous behavior | ||
|
|
||
| Previously, the affected APIs compiled with an obsolete warning. Most of the warnings pointed at a replacement API. For example, calling `EditContext.AddDataAnnotationsValidation()` produced an obsolete warning recommending `EnableDataAnnotationsValidation`. | ||
|
|
||
| ## New behavior | ||
|
|
||
| In ASP.NET Core 11, the following APIs are removed and no longer compile. | ||
|
|
||
| ### Microsoft.AspNetCore.Components | ||
|
|
||
| - `Router.PreferExactMatches` property. The property has had no effect since exact-match routing became the default in .NET 6. | ||
| - `ResourceAsset(string url)` constructor overload. | ||
|
|
||
| ### Microsoft.AspNetCore.Components.Forms | ||
|
|
||
| - `EditContextDataAnnotationsExtensions.AddDataAnnotationsValidation(EditContext)` extension method. | ||
| - `EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(EditContext)` overload (the overload without an `IServiceProvider` parameter). | ||
| - `RemoteBrowserFileStreamOptions` class. | ||
|
|
||
| ### Microsoft.AspNetCore.Components.Web | ||
|
|
||
| - `WebEventCallbackFactoryEventArgsExtensions` class and all of its extension methods. | ||
| - The `init` accessor on `WebRenderer.RendererId`. The property is now set only via constructor. | ||
|
|
||
| ### Microsoft.AspNetCore.Components.WebAssembly | ||
|
|
||
| - `JSInteropMethods.NotifyLocationChanged(string, bool)` method. | ||
|
|
||
| ### Microsoft.AspNetCore.Components.WebAssembly.Authentication | ||
|
|
||
| - `SignOutSessionStateManager` class. The class has been a no-op since .NET 7. | ||
| - `RemoteAuthenticationService` constructor overload that doesn't take a logger. | ||
| - `AccessTokenResult` constructor and the `RedirectUrl` property. | ||
| - `AccessTokenNotAvailableException` now uses `InteractiveRequestUrl` in place of the removed `RedirectUrl`. | ||
|
|
||
| ## Type of breaking change | ||
|
|
||
| This change can affect [source compatibility](/dotnet/core/compatibility/categories#source-compatibility). | ||
|
|
||
| ## Reason for change | ||
|
|
||
| These APIs were marked obsolete in earlier releases (most between .NET 5 and .NET 8). Each had a documented replacement, and continuing to ship the obsolete surface area increased maintenance cost and surface area for Blazor without benefit. For more information, see [dotnet/aspnetcore#62755](https://github.com/dotnet/aspnetcore/pull/62755). | ||
|
|
||
| ## Recommended action | ||
|
|
||
| Update your code to use the replacement APIs as listed in the following table. | ||
|
|
||
| | Removed API | Replacement | | ||
| |-------------|-------------| | ||
| | `Router.PreferExactMatches` | Remove the assignment. Exact matching has been the default since .NET 6. | | ||
| | `ResourceAsset(string url)` | `ResourceAsset(string url, IReadOnlyList<ResourceAssetProperty>? properties)` | | ||
| | `EditContextDataAnnotationsExtensions.AddDataAnnotationsValidation(EditContext)` | `EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(EditContext, IServiceProvider)` | | ||
| | `EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(EditContext)` | `EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation(EditContext, IServiceProvider)` | | ||
| | `RemoteBrowserFileStreamOptions` | `BrowserFileStreamOptions` | | ||
| | `WebEventCallbackFactoryEventArgsExtensions` | Use `EventCallbackFactory` directly. | | ||
| | `WebRenderer.RendererId.init` accessor | Pass `RendererId` via the `WebRenderer` constructor. | | ||
| | `JSInteropMethods.NotifyLocationChanged(string, bool)` | `JSInteropMethods.NotifyLocationChanged(string uri, string? state, bool isInterceptedLink)` (added in .NET 7). This method is for framework use only. | | ||
| | `SignOutSessionStateManager` | Delete usages. The class has been a no-op since .NET 7. | | ||
| | `RemoteAuthenticationService` (constructor without logger) | The remaining constructor takes an `ILogger<RemoteAuthenticationService<...>>` parameter. | | ||
| | `AccessTokenResult` constructor and `RedirectUrl` property | Use the remaining constructor and `InteractiveRequestUrl`. | | ||
|
|
||
| ## Affected APIs | ||
|
|
||
| - <xref:Microsoft.AspNetCore.Components.Routing.Router.PreferExactMatches?displayProperty=fullName> | ||
| - <xref:Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.AddDataAnnotationsValidation*?displayProperty=fullName> | ||
| - <xref:Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation*?displayProperty=fullName> | ||
|
guardrex marked this conversation as resolved.
Outdated
|
||
| - `Microsoft.AspNetCore.Components.Forms.RemoteBrowserFileStreamOptions` | ||
| - `Microsoft.AspNetCore.Components.Web.WebEventCallbackFactoryEventArgsExtensions` | ||
| - `Microsoft.AspNetCore.Components.RenderTree.WebRenderer.RendererId` (init accessor) | ||
| - `Microsoft.AspNetCore.Components.WebAssembly.Infrastructure.JSInteropMethods.NotifyLocationChanged(System.String, System.Boolean)` | ||
| - `Microsoft.AspNetCore.Components.WebAssembly.Authentication.SignOutSessionStateManager` | ||
| - `Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions>` (constructor overload without logger) | ||
| - `Microsoft.AspNetCore.Components.WebAssembly.Authentication.AccessTokenResult` (legacy constructor and `RedirectUrl` property) | ||
| - `Microsoft.AspNetCore.Components.ResourceAsset` (constructor overload without `properties`) | ||
101 changes: 101 additions & 0 deletions
101
aspnetcore/breaking-changes/11/concurrencylimiter-removed.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| --- | ||
| title: "Breaking change: ConcurrencyLimiter middleware removed" | ||
| description: "Learn about the breaking change in ASP.NET Core 11 where the Microsoft.AspNetCore.ConcurrencyLimiter package and middleware are removed. Use the rate-limiting middleware instead." | ||
| ms.date: 06/04/2026 | ||
| ai-usage: ai-assisted | ||
| --- | ||
|
|
||
|
guardrex marked this conversation as resolved.
Outdated
|
||
| # ConcurrencyLimiter middleware removed | ||
|
|
||
| The `Microsoft.AspNetCore.ConcurrencyLimiter` package and its middleware have been removed from ASP.NET Core 11. The middleware was marked obsolete in ASP.NET Core 8 in favor of the rate-limiting middleware (`Microsoft.AspNetCore.RateLimiting`), which exposes the equivalent concurrency-limiting functionality through `System.Threading.RateLimiting`. | ||
|
|
||
| ## Version introduced | ||
|
|
||
| .NET 11 Preview 1 | ||
|
|
||
| ## Previous behavior | ||
|
|
||
| Previously, you could install the [`Microsoft.AspNetCore.ConcurrencyLimiter`](https://www.nuget.org/packages/Microsoft.AspNetCore.ConcurrencyLimiter) NuGet package and use the `UseConcurrencyLimiter` extension method together with one of the queue policies (`AddQueuePolicy` or `AddStackPolicy`) to bound how many requests could run concurrently: | ||
|
|
||
| ```csharp | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.ConcurrencyLimiter; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
|
|
||
| builder.Services.AddQueuePolicy(options => | ||
| { | ||
| options.MaxConcurrentRequests = 2; | ||
| options.RequestQueueLimit = 25; | ||
| }); | ||
|
|
||
| var app = builder.Build(); | ||
|
|
||
| app.UseConcurrencyLimiter(); | ||
| app.Run(async context => await context.Response.WriteAsync("Hello World!")); | ||
| app.Run(); | ||
| ``` | ||
|
|
||
| The middleware emitted a build warning that directed you to the rate-limiting middleware, but it still worked at runtime. | ||
|
|
||
| ## New behavior | ||
|
|
||
| Starting in ASP.NET Core 11, the `Microsoft.AspNetCore.ConcurrencyLimiter` middleware source is removed from the ASP.NET Core repository, and no `Microsoft.AspNetCore.ConcurrencyLimiter` 11.x or later package is published. The following types and methods are no longer available to apps that target `net11.0`: | ||
|
|
||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterMiddleware` | ||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterOptions` | ||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.IQueuePolicy` | ||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions` | ||
| - `Microsoft.AspNetCore.Builder.ConcurrencyLimiterExtensions` (including the `UseConcurrencyLimiter` extension method) | ||
| - `Microsoft.Extensions.DependencyInjection.QueuePolicyServiceCollectionExtensions` (including `AddQueuePolicy` and `AddStackPolicy`) | ||
|
|
||
| Existing 9.x and 10.x package versions remain on NuGet and continue to work for projects that still target `net9.0` or `net10.0`. Projects that take an explicit `PackageReference` and retarget to `net11.0` get a NuGet restore error because there's no 11.x package version. Code that references these types fails to compile against ASP.NET Core 11. | ||
|
|
||
| ## Type of breaking change | ||
|
|
||
| This change can affect [binary compatibility](/dotnet/core/compatibility/categories#binary-compatibility) and [source compatibility](/dotnet/core/compatibility/categories#source-compatibility). | ||
|
|
||
| ## Reason for change | ||
|
|
||
| The `ConcurrencyLimiter` middleware was marked obsolete in ASP.NET Core 8 (diagnostic `ASP0025`) because its functionality is covered by the rate-limiting middleware, which is built on the more general-purpose `System.Threading.RateLimiting` APIs. Maintaining two parallel implementations adds friction without benefit. For more information, see [dotnet/aspnetcore#64020](https://github.com/dotnet/aspnetcore/pull/64020). | ||
|
|
||
| ## Recommended action | ||
|
|
||
| Migrate to the [rate-limiting middleware](/aspnet/core/performance/rate-limit), which provides an equivalent concurrency limiter built on <xref:System.Threading.RateLimiting.ConcurrencyLimiter?displayProperty=nameWithType>: | ||
|
|
||
| ```csharp | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using System.Threading.RateLimiting; | ||
|
|
||
| var builder = WebApplication.CreateBuilder(args); | ||
|
|
||
| builder.Services.AddRateLimiter(options => | ||
| { | ||
| options.AddConcurrencyLimiter("ConcurrencyPolicy", limiterOptions => | ||
| { | ||
| limiterOptions.PermitLimit = 2; | ||
| limiterOptions.QueueLimit = 25; | ||
| limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; | ||
| }); | ||
| }); | ||
|
|
||
| var app = builder.Build(); | ||
|
|
||
| app.UseRateLimiter(); | ||
| app.MapGet("/", () => "Hello World!").RequireRateLimiting("ConcurrencyPolicy"); | ||
| app.Run(); | ||
| ``` | ||
|
|
||
| If you can't migrate immediately, you can stay on a `Microsoft.AspNetCore.ConcurrencyLimiter` 9.x or 10.x [NuGet package](https://www.nuget.org/packages/Microsoft.AspNetCore.ConcurrencyLimiter) while you target `net11.0`. No new versions of the package will ship for .NET 11 or later. | ||
|
|
||
| ## Affected APIs | ||
|
|
||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterMiddleware` | ||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterOptions` | ||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.IQueuePolicy` | ||
| - `Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions` | ||
| - `Microsoft.AspNetCore.Builder.ConcurrencyLimiterExtensions.UseConcurrencyLimiter` | ||
| - `Microsoft.Extensions.DependencyInjection.QueuePolicyServiceCollectionExtensions.AddQueuePolicy` | ||
| - `Microsoft.Extensions.DependencyInjection.QueuePolicyServiceCollectionExtensions.AddStackPolicy` | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.