Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: "Breaking change: Blazor custom event registration throws when name matches a browser event"
ai-usage: ai-assisted
description: "Learn about the breaking change in ASP.NET Core 11 where Blazor.registerCustomEventType throws when eventName equals browserEventName."
ms.date: 06/04/2026
---
# 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).
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
title: "Breaking change: Blazor enhanced navigation no longer preloads resources"
ai-usage: ai-assisted
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
---
# 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.
88 changes: 88 additions & 0 deletions aspnetcore/breaking-changes/11/blazor-obsolete-apis-removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: "Breaking change: Obsolete Blazor APIs removed"
ai-usage: ai-assisted
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
---
# 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%2A?displayProperty=fullName>
- <xref:Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions.EnableDataAnnotationsValidation%2A?displayProperty=fullName>
- `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`)
100 changes: 100 additions & 0 deletions aspnetcore/breaking-changes/11/concurrencylimiter-removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: "Breaking change: ConcurrencyLimiter middleware removed"
ai-usage: ai-assisted
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
---
# 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`
Loading
Loading