Edit

What's new in ASP.NET Core in .NET 11

This article highlights the most significant changes in ASP.NET Core in .NET 11 with links to relevant documentation.

This article will be updated as new preview releases are made available.

Blazor

This section describes new features for Blazor.

New DisplayName component and support for [Display] and [DisplayName] attributes

The DisplayName component can be used to display property names from metadata attributes:

[Required, DisplayName("Production Date")]
public DateTime ProductionDate { get; set; }

The [Display] attribute on the model class property is supported:

[Required, Display(Name = "Production Date")]
public DateTime ProductionDate { get; set; }

Of the two approaches, the [Display] attribute is recommended, which makes additional properties available. The [Display] attribute also enables assigning a resource type for localization. When both attributes are present, [Display] takes precedence over [DisplayName]. If neither attribute is present, the component falls back to the property name.

Use the DisplayName component in labels or table headers:

<label>
    <DisplayName For="@(() => Model!.ProductionDate)" />
    <InputDate @bind-Value="Model!.ProductionDate" />
</label>

Blazor Web script startup options format now supported for Blazor Server and Blazor WebAssembly scripts

The Blazor Web App script (blazor.web.js) options object passed to Blazor.start() uses the following format since the release of .NET 8:

Blazor.start({
  ssr: { ... },
  circuit: { ... },
  webAssembly: { ... },
});

Now, Blazor Server (blazor.server.js) and Blazor WebAssembly (blazor.webassembly.js) scripts can use the same options format.

The following example shows the prior options format, which remains supported:

Blazor.start({
  loadBootResource: function (...) {
      ...
    },
  });

The newly supported options format for the preceding example:

Blazor.start({
  webAssembly: {
    loadBootResource: function (...) {
      ...
    },
  },
});

For more information, see ASP.NET Core Blazor startup.

New BasePath component

Blazor Web Apps can use the new BasePath component (<BasePath />) to render the app's app base path (<base href>) HTML tag automatically. For more information, see ASP.NET Core Blazor app base path.

Inline JS event handler removed from the NavMenu component

The inline JS event handler that toggles the display of navigation links is no longer present in the NavMenu component of the Blazor Web App project template. Apps generated from the project template now use a collocated JS module approach to show or hide the navigation bar on the rendered page. The new approach improves Content Security Policy (CSP) compliance because it doesn't require the CSP to include an unsafe hash for the inline JS.

To migrate an existing app to .NET 11, including adopting the new JS module approach for the navigation bar toggler, see Migrate from ASP.NET Core in .NET 10 to ASP.NET Core in .NET 11.

The new RelativeToCurrentUri parameter (default: false) for NavigationManager.NavigateTo and the NavLink component allows you to navigate to URIs relative to the current page path rather than the app's base URI.

Consider the following nested endpoints:

  • /docs
    • /getting-started
      • /installation
      • /configuration

When the browser's URI is /docs/getting-started/installation and you want to navigate the user to /docs/getting-started/configuration, NavigateTo("/configuration") redirects to /configuration at the app's root instead of the relative path at /docs/getting-started/configuration. Set the RelativeToCurrentUri with NavigateTo or the NavLink component for the desired navigation:

Navigation.NavigateTo("/configuration", new NavigationOptions
{
    RelativeToCurrentUri = true
});
<NavLink href="configuration" RelativeToCurrentUri="true">Configuration</NavLink>

Persist temporary data between HTTP requests during static server-side rendering (static SSR)

To persist temporary data between HTTP requests during static server-side rendering (static SSR), Blazor supports TempData. TempData is ideal for scenarios such as flash messages after form submissions, passing data during redirects (POST-Redirect-GET pattern), and one-time notifications.

TempData is available when AddRazorComponents is called in the app's Program file and is provided as a cascading value with the [CascadingParameter] attribute.

[CascadingParameter]
public ITempData? TempData { get; set; }

When supplied to a parameter for simple read/write of a single value, use the [SupplyParameterFromTempData] attribute:

[SupplyParameterFromTempData]
public string? Message { get; set; }

For more information, see ASP.NET Core Blazor server-side state management.

New Blazor Web Worker template (blazorwebworker)

Blazor WebAssembly apps can perform heavy computing on the client, but doing so on the UI thread interferes with UI rendering and negatively affects the user experience. In .NET 10, we added an article with a sample app to make offloading heavy work from the UI thread to a Web Worker easier. For .NET 11, we've added the Blazor Web Worker project template (blazorwebworker), which provides infrastructure for running .NET code in a Web Worker in Blazor WebAssembly apps. For more information, see ASP.NET Core Blazor with .NET on Web Workers.

Virtualization enhancements

  • The Virtualize<TItem> component no longer assumes every item has the same height. The component now adapts to measured item sizes at runtime, which reduces incorrect spacing and scrolling when item heights vary. These updates include an update to the default value of Virtualize<TItem>.OverscanCount, which was 3 in .NET 10 or earlier and now changes to 15 in .NET 11 or later. The change in default value increases the precision of average item height calculations.

    For more information, see the Item size section and Overscan count section of the Virtualization article.

  • Use the new AnchorMode parameter to control how the viewport behaves at list edges when items are dynamically added:

    • None: No edge pinning. The viewport stays at the current scroll position regardless of item changes.
    • Beginning: Pins the viewport to the beginning of the list. For example, this pinning behavior is useful for a news feed user experience.
    • End: Pins the viewport to the end of the list. For example, this pinning behavior is useful for a chat or logging user experience.

    In the following example, the virtualized content is pinned to the beginning of the list:

    <Virtualize AnchorMode="Beginning" ...>
        ...
    </Virtualize>
    

    For more information, see ASP.NET Core Razor component virtualization.

New service defaults library project template for Blazor WebAssembly apps

The blazor-wasm-servicedefaults project template creates a service defaults library for Blazor WebAssembly apps with .NET Aspire integration. For more information, see Tooling for ASP.NET Core Blazor.

New development server for Blazor WebAssembly apps

Microsoft.AspNetCore.Components.Gateway is a lightweight ASP.NET Core host that replaces Microsoft.AspNetCore.Components.WebAssembly.DevServer for serving standalone Blazor WebAssembly applications during development and production.

For more information, see [Blazor] Replace DevServer with BlazorGateway for standalone WASM apps (dotnet/aspnetcore #65982).

Server-triggered circuit pause

This feature applies to server-side Blazor apps.

Blazor already supports graceful circuit pause and resume with Blazor.pauseCircuit() and Blazor.resumeCircuit(). .NET 11 introduces a symmetric server-side pause and resume capability, where the server can request that connected clients begin the graceful circuit-pause flow.

Circuit.RequestCircuitPauseAsync(CancellationToken) is used to request that the connected client begin the graceful circuit-pause flow. The CancellationToken cancels the request before it is accepted by the framework. The method returns true if the request was accepted and the client was asked to begin pausing.

This feature is useful in the following scenarios:

  • Planned shutdowns and deployments.
  • Instance draining.
  • App maintenance windows.

For more information and an implementation example for server restarts, see ASP.NET Core Blazor server-side state management.

Blazor Hybrid

This section describes new features for Blazor Hybrid.

Release notes appear in this section as preview features become available.

SignalR

This section describes new features for SignalR.

Minimal APIs

This section describes new features for Minimal APIs.

Endpoint filters observe parameter-binding failures

When a minimal API endpoint has any filters or filter factories configured, the filter pipeline now runs even if parameter binding fails. Filters can read HttpContext.Response.StatusCode == 400 and substitute their own response body.

In the Development environment, set RouteHandlerOptions.ThrowOnBadRequest = false so the framework returns a 400 that the filter can observe instead of throwing BadHttpRequestException to the developer exception page. This is already the default in non-Development environments.

Thank you @marcominerva for this contribution!

OpenAPI

This section describes new features for OpenAPI.

Describe binary file responses

ASP.NET Core 11 introduces support for generating OpenAPI descriptions for operations that return binary file responses. This support maps the FileContentResult result type to an OpenAPI schema with type: string and format: binary.

Use the Produces<T> extension method with T of FileContentResult to specify the response type and content type:

app.MapPost("/filecontentresult", () =>
{
    var content = "This endpoint returns a FileContentResult!"u8.ToArray();
    return TypedResults.File(content);
})
.Produces<FileContentResult>(contentType: MediaTypeNames.Application.Octet);

The generated OpenAPI document describes the endpoint response as:

responses:
  '200':
    description: OK
    content:
      application/octet-stream:
        schema:
          $ref: '#/components/schemas/FileContentResult'

The FileContentResult is defined in components/schemas as:

components:
  schemas:
    FileContentResult:
      type: string
      format: binary

OpenAPI 3.2.0 support (Breaking Change)

Microsoft.AspNetCore.OpenApi now supports OpenAPI 3.2.0 through an updated dependency on Microsoft.OpenApi 3.3.1. This update includes breaking changes from the underlying library. For more information, see the Microsoft.OpenApi upgrade guide.

To generate an OpenAPI 3.2.0 document, specify the version when calling AddOpenApi:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_2;
});

Subsequent updates take advantage of new capabilities in the 3.2.0 specification, such as item schema support for streaming events.

Thank you @baywet for this contribution!

HTTP QUERY in generated OpenAPI documents

OpenAPI document generation now recognizes HTTP QUERY as a known operation type. QUERY is a proposed safe, idempotent method that lets clients send a request body when describing a search, useful when a query is too large or too structured to fit in a URL. Routing already accepts arbitrary verb strings via MapMethods, and OpenAPI 3.2 adds a query field to the Path Item Object so this can be described in the OpenAPI document.

Note that query is only valid in an OpenAPI 3.2 document, so set the OpenApiVersion in the OpenApiOptions. In earlier OpenAPI versions, the query operation is generated within an x-oai-additionalOperations specification extension in the Path Item Object.

using Microsoft.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_2;
});

var app = builder.Build();

app.MapOpenApi();

app.MapMethods("/search", ["QUERY"], (SearchRequest request) =>
    SearchService.Run(request));

app.Run();

In an OpenAPI 3.2 document, the QUERY operation is described inline as a sibling of get, post, and other standard operations:

"paths": {
  "/search": {
    "query": {
      "requestBody": { ... },
      "responses": { "200": { ... } }
    }
  }
}

In OpenAPI 3.0 and 3.1 documents, the same operation is represented under the x-oai-additionalOperations extension on the Path Item:

"paths": {
  "/search": {
    "x-oai-additionalOperations": {
      "QUERY": {
        "requestBody": { ... },
        "responses": { "200": { ... } }
      }
    }
  }
}

Thank you @kilifu for this contribution!

File stream result types appear in OpenAPI documents

FileStreamResult, FileContentHttpResult, and FileStreamHttpResult are now described as binary string schemas in generated OpenAPI documents, so clients see accurate response shapes for endpoints that stream files. Annotate the endpoint with .Produces<FileContentHttpResult>(contentType: "application/pdf") (or the equivalent FileStreamHttpResult/FileStreamResult type) so OpenAPI sees the result type and emits the binary schema.

Thank you @marcominerva for this contribution!

Authentication and authorization

This section describes new features for authentication and authorization.

TimeProvider support in ASP.NET Core Identity

ASP.NET Core Identity now uses TimeProvider instead of DateTime and DateTimeOffset for all time-related operations. This change makes Identity components more testable and provides better control over time in tests and specialized scenarios.

The following example shows how to use a fake TimeProvider for testing Identity features:

// In tests
var fakeTimeProvider = new FakeTimeProvider(
    new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero));

services.AddSingleton<TimeProvider>(fakeTimeProvider);
services.AddIdentity<IdentityUser, IdentityRole>();

// Identity will now use the fake time provider

By using TimeProvider, you can more easily write deterministic tests for time-sensitive Identity features such as token expiration, lockout durations, and security stamp validation.

Infer passkey display name from authenticator

ASP.NET Core Identity now automatically infers friendly display names for passkeys based on their AAGUID (Authenticator Attestation GUID). Built-in mappings are included for the most commonly used passkey authenticators, including Google Password Manager, iCloud Keychain, Windows Hello, 1Password, and Bitwarden.

For known authenticators, the name is automatically assigned without prompting the user. For unknown authenticators, the user is redirected to a rename page. Extend the mappings by adding entries to the PasskeyAuthenticators dictionary in the project.

Miscellaneous

This section describes miscellaneous new features in .NET 11.

IOutputCachePolicyProvider interface

ASP.NET Core in .NET 11 provides the [IOutputCachePolicyProvider](https://source.dot.net/#Microsoft.AspNetCore.OutputCaching/[IOutputCachePolicyProvider.cs](https://source.dot.net/#Microsoft.AspNetCore.OutputCaching/IOutputCachePolicyProvider.cs)` interface for implementing custom output caching policy selection logic. By using this interface, apps can determine the default base caching policy, check for the existence of named policies, and support advanced scenarios where policies must be resolved dynamically. Examples include loading policies from external configuration sources, databases, or applying tenant-specific caching rules.

The following code shows the IOutputCachePolicyProvider interface:

public interface IOutputCachePolicyProvider
{
    IReadOnlyList<IOutputCachePolicy> GetBasePolicies();
    ValueTask<IOutputCachePolicy?> GetPolicyAsync(string policyName);
}

Thank you @lqlive for this contribution!

Auto-trust development certificates in WSL

The development certificate setup now automatically trusts certificates in WSL (Windows Subsystem for Linux) environments. When you run dotnet dev-certs https --trust in WSL, the certificate is automatically installed and trusted in both the WSL environment and Windows, eliminating manual trust configuration.

# Automatically trusts certificates in both WSL and Windows
dotnet dev-certs https --trust

This improvement streamlines the development experience when using WSL, removing a common friction point for developers working in Linux environments on Windows.

Thank you @StickFun for this contribution!

Native OpenTelemetry tracing for ASP.NET Core

ASP.NET Core now natively adds OpenTelemetry semantic convention attributes to the HTTP server activity, aligning with the OpenTelemetry HTTP server span specification. All required attributes are included by default, matching the metadata previously only available through the OpenTelemetry.Instrumentation.AspNetCore library.

To collect the built-in tracing data, subscribe to the Microsoft.AspNetCore activity source in your OpenTelemetry configuration:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("Microsoft.AspNetCore")
        .AddConsoleExporter());

No additional instrumentation library (such as OpenTelemetry.Instrumentation.AspNetCore) is needed. The framework now directly populates semantic convention attributes on the request activity, such as http.request.method, url.path, http.response.status_code, and server.address.

If you don't want OpenTelemetry attributes added to the activity, you can turn it off by setting the Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData AppContext switch to true.

Performance improvements

Kestrel's HTTP/1.1 request parser now uses a non-throwing code path for handling malformed requests. Instead of throwing BadHttpRequestException on every parse failure, the parser returns a result struct indicating success, incomplete, or error states. In scenarios with many malformed requests — such as port scanning, malicious traffic, or misconfigured clients — this eliminates expensive exception handling overhead and improves throughput by up to 20-40%. There's no impact on valid request processing.

The HTTP logging middleware now pools its ResponseBufferingStream instances, reducing per-request allocations when response body logging or interceptors are enabled.

Zstandard response compression and request decompression

ASP.NET Core now supports Zstandard (zstd) for both response compression and request decompression. This adds zstd support to the existing response-compression and request-decompression middleware and enables zstd by default.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCompression();
builder.Services.AddRequestDecompression();
builder.Services.Configure<ZstandardCompressionProviderOptions>(options =>
{
    options.CompressionOptions = new ZstandardCompressionOptions
    {
        Quality = 6 // 1-22, higher = better compression, slower
    };
});

Thank you @manandre for this contribution!

HTTP/3 starts processing requests earlier

Kestrel now starts processing HTTP/3 requests without waiting for the control stream and SETTINGS frame first, which reduces first-request latency on new connections.

MCP Server template ships with the .NET SDK

The mcpserver project template, previously available only by installing Microsoft.McpServer.ProjectTemplates, now ships as a bundled template in the .NET SDK:

dotnet new mcpserver -o MyMcpServer

Moving the template into ASP.NET Core makes it discoverable from dotnet new list without a separate install step, and aligns its servicing with the rest of the web stack.

TLS handshake observability in Kestrel

Two related changes make it easier to diagnose and customize TLS connections in Kestrel.

ITlsHandshakeFeature now exposes an Exception property containing the exception thrown during a failed TLS handshake, so middleware and logging can record why a connection failed instead of seeing a bare IOException further up the stack. The feature continues to work after the handshake fails — Kestrel snapshots the relevant fields off the underlying SslStream before it is disposed.

The TlsClientHelloBytesCallback option on HttpsConnectionAdapterOptions was reworked as a connection middleware. The previous callback shape is now obsolete; configure ClientHello inspection via the new ListenOptions.UseTlsClientHelloListener extension instead. The example below uses both features together — connection middleware reads ITlsHandshakeFeature.Exception after the handshake, and UseTlsClientHelloListener inspects the ClientHello before TLS:

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.Use(next => async context =>
        {
            await next(context);

            var tlsHandshakeFeature = context.Features.Get<ITlsHandshakeFeature>();
            if (tlsHandshakeFeature?.Exception is { } ex)
            {
                Console.WriteLine($"[TLS Handshake Failed] ConnectionId={context.ConnectionId}, Exception={ex.GetType().Name}: {ex.Message}");
            }
        });

        // UseTlsClientHelloListener must be called before UseHttps()
        listenOptions.UseTlsClientHelloListener((connection, clientHelloBytes) =>
        {
            Console.WriteLine($"TLS Client Hello received on {connection.ConnectionId}, {clientHelloBytes.Length} bytes");
        });
        listenOptions.UseHttps();
    });
});

Response compression always emits Vary: Accept-Encoding

The response-compression middleware now adds Vary: Accept-Encoding to every response when compression is enabled, even when the response itself isn't compressed. This prevents shared caches and CDNs from serving a compressed payload to a client that didn't ask for one (or vice versa).

Thank you @pedrobsaila for this contribution!

Runtime-async enabled for shared framework libraries

ASP.NET Core's shared-framework-only libraries are now compiled with the runtime-async feature on net11.0+. Runtime-async lets the runtime, rather than the C# compiler, generate the state machine for async/await, which can reduce per-await allocations and improve diagnostics. This is an internal codegen change with no public API impact — apps targeting net11.0 automatically benefit when they call into the affected ASP.NET Core libraries.

Libraries that ship as both shared-framework members and standalone NuGet packages are excluded, because runtime-async is incompatible with WebAssembly and would otherwise break Wasm consumers of those packages.

Because runtime-async changes how async/await is generated for a large portion of the ASP.NET Core stack, try your apps against this preview and file an issue if you hit unexpected behavior, particularly around exception stacks, ExecutionContext/AsyncLocal flow, or anything that looks like a regression from .NET 10.

Rate-limiting middleware returns accurate Retry-After headers

The FixedWindowRateLimiter now reports a RetryAfter metadata value that accurately reflects the next window boundary. Apps that propagate this metadata to the Retry-After response header in their OnRejected callback now produce correct retry intervals automatically, with no code changes required.

Additional fixes in System.Threading.RateLimiting resolve an issue where TokenBucketRateLimiter mishandled partial token refills during zero-permit acquisition, and improve the chained rate limiter returned by CreateChained to correctly forward idle-duration and replenishment behavior from its inner limiters.

For an overview of the rate limiting middleware, see Rate limiting middleware in ASP.NET Core.

Thank you @asbjornvad and @apoorvdarshan for these contributions!

Breaking changes

Use the articles in Breaking changes in .NET to find breaking changes that might apply when upgrading an app to a newer version of .NET.