.NET 11 Preview 1 Is Here: What's New and What to Expect

The .NET team just released the first preview of .NET 11 and there is already a lot to talk about. While this is an early preview - not a release candidate - the breadth of changes touching the runtime, libraries, ASP.NET Core, Blazor, C#, the SDK and Entity Framework Core gives a clear picture of where the platform is heading. Let me walk you through the most important changes you should know about.

Note: .NET 11 is currently in preview. Not all features described here are complete or ready for production use yet. Upcoming previews will continue to expand on these foundations.


Runtime: Async Gets a New Engine

The single most significant runtime investment in .NET 11 Preview 1 is Runtime Async. This is a new runtime-level infrastructure for async methods, designed to improve both performance and diagnostics for async-heavy code paths.

What is Runtime Async?

Today, the C# compiler transforms every async method into a state machine at compile time. Runtime Async changes this approach by moving the async machinery into the runtime itself, letting the runtime manage continuation scheduling directly. The goal is to reduce overhead in hot async paths and enable better tooling (e.g., more accurate stack traces and profiling of async workloads).

What is available in Preview 1?

  • CoreCLR support is enabled by default. You no longer need environment variable overrides to activate the runtime side.
  • Native AOT support for runtime-async code is now included, meaning AOT-compiled applications can work with this feature.
  • The core runtime libraries are not yet compiled with runtime-async support - that is expected in upcoming previews.

To experiment with runtime-async at the compiler level today, you need two project file settings:

1<EnablePreviewFeatures>true</EnablePreviewFeatures>
2<Features>$(Features);runtime-async=on</Features>

This is the kind of fundamental change that can pay dividends across every async-heavy workload - web applications, background services, anything that relies heavily on Task and await.


Runtime: CoreCLR Comes to WebAssembly

.NET 11 begins foundational work for bringing CoreCLR to WebAssembly. Currently, Blazor WebAssembly runs on the Mono runtime. Bringing CoreCLR to WASM would eventually align the performance and feature set of server and browser-side .NET more closely and opens the door to running AOT-compiled CoreCLR code directly in the browser.

Preview 1 lays the groundwork by starting a WASM-targeting RyuJIT (the .NET JIT compiler). This is not yet ready for general use - think of it as the team publicly tracking their progress on a major multi-year goal.

Similarly, the CoreCLR Interpreter work aims to bring CoreCLR to platforms that do not support runtime code generation, including iOS and WASM environments where JIT is prohibited. Again, Preview 1 establishes the foundation; full availability will come in later previews.


Runtime: JIT and GC Improvements

Beyond the headlining features, Preview 1 ships a range of JIT performance improvements:

  • Multicore JIT MAX_METHODS limit raised - larger applications start up faster by letting more methods be JIT-compiled in parallel.
  • stackalloc zeroing optimized on Arm64 - reduces instruction count for stack allocations via more efficient STORE_BLK usage.
  • Improved induction-variable (IV) analysis - enables more loop optimizations in the JIT.
  • Devirtualization of non-shared generic virtual methods - reduces virtual dispatch overhead and enables further inlining.
  • Bitfield extraction from parameter registers - better codegen for certain struct/argument shapes without unnecessary spilling.

On the Garbage Collector side, the GC heap hard limit (GCHeapHardLimit) now works in 32-bit processes, giving developers better control over heap growth in memory-constrained scenarios.

C#: Collection Expression Arguments

C# 14 introduces collection expression arguments, which allow passing construction parameters when creating a collection via a collection expression. The motivating scenario: how do you specify the initial capacity when using the clean [...] syntax?

1// Initialize with twice the capacity since we'll add more values later
2List<string> names = [with(capacity: values.Count * 2), .. values];

The with(...) clause inside the collection expression passes special arguments to the collection’s constructor. This is especially powerful for dictionaries, where you might want to specify a custom IEqualityComparer:

1var lookup = new Dictionary<string, int>([with(StringComparer.OrdinalIgnoreCase), "Alice": 1, "Bob": 2]);

Extended Layout Support

The second C# 14 feature in Preview 1 is extended layout support for [StructLayout]. This gives developers finer control over how fields are laid out in memory in struct types - important for low-level interop scenarios and performance-sensitive data structures.


SDK: Quality-of-Life Improvements

dotnet run - Interactive Target Framework Selection

If a project targets multiple frameworks (e.g., net10.0 and net11.0), dotnet run now presents an interactive picker when no -f argument is given, rather than silently picking the first framework or erroring out.

dotnet test - Positional Arguments

dotnet test now accepts the test project or assembly path as a positional argument, so you can write:

1dotnet test MyTests.csproj

instead of requiring explicit flags.

dotnet watch Improvements

dotnet watch now supports Hot Reload for reference changes (changes in referenced projects trigger a reload) and allows configuring the port it listens on - useful in CI or multi-project local setups.

New Analyzers

Several new Roslyn analyzers have been added to guide developers toward better APIs and patterns. One example is the new Blazor analyzer discussed below.


ASP.NET Core and Blazor

This is where Preview 1 is especially rich. Let’s go through the highlights.

EnvironmentBoundary Component

Blazor gets a first-class component for conditionally rendering content based on the hosting environment - analogous to MVC’s <environment> tag helper:

1<EnvironmentBoundary Include="Development">
2    <div class="alert alert-warning">Debug mode enabled</div>
3</EnvironmentBoundary>
4
5<EnvironmentBoundary Exclude="Production">
6    <p>Current time: @DateTime.Now</p>
7</EnvironmentBoundary>

The component supports Include and Exclude parameters with case-insensitive, comma-separated environment names. It works consistently in both Blazor Server and Blazor WebAssembly by injecting IHostEnvironment under the hood.

Label Component for Forms

A new Label component for Blazor EditForms automatically renders accessible labels. It reads display names from [Display] and [DisplayName] attributes, falling back to the property name. Two patterns are supported:

Nested (implicit association):

1<Label For="() => model.Name">
2    <InputText @bind-Value="model.Name" />
3</Label>

Explicit for/id association:

1<Label For="() => model.Name" />
2<InputText @bind-Value="model.Name" />

All built-in input components now automatically generate id attributes, so the for/id wiring works correctly out of the box.

DisplayName Component

Similar to MVC’s @Html.DisplayNameFor(), the new DisplayName component renders the display name of a property from its metadata:

1<table>
2    <thead>
3        <tr>
4            <th><DisplayName For="() => product.Name" /></th>
5            <th><DisplayName For="() => product.Price" /></th>
6            <th><DisplayName For="() => product.ReleaseDate" /></th>
7        </tr>
8    </thead>
9</table>

The lookup order is: [Display(Name = "...")][DisplayName("...")] → property name. Localization via [Display(ResourceType = ...)] is also supported.

QuickGrid - OnRowClick Event

The QuickGrid component now supports row click events. Setting OnRowClick automatically applies a pointer cursor to rows and invokes your callback with the clicked item:

 1<QuickGrid Items="@people.AsQueryable()" OnRowClick="@HandleRowClick">
 2    <PropertyColumn Property="@(p => p.Name)" />
 3    <PropertyColumn Property="@(p => p.Email)" />
 4</QuickGrid>
 5
 6@code {
 7    void HandleRowClick(Person person)
 8    {
 9        NavigationManager.NavigateTo($"/person/{person.Id}");
10    }
11}

Relative Navigation with RelativeToCurrentUri

NavigationManager.NavigateTo() and NavLink gain a RelativeToCurrentUri option. When enabled, navigation is resolved relative to the current page path rather than the application root - useful for documentation sites, wizards, or any deeply nested routing scenario:

1// When at /docs/getting-started/installation, this navigates to
2// /docs/getting-started/configuration (not /configuration)
3NavigationManager.NavigateTo("configuration", new NavigationOptions
4{
5    RelativeToCurrentUri = true
6});

GetUriWithHash() Extension Method

A zero-allocation helper for appending a hash fragment to the current URI:

1<a href="@Navigation.GetUriWithHash("section-1")">Jump to Section 1</a>

BasePath Component

Instead of hardcoding <base href="/"> in your HTML host page, you can now use the BasePath component, which resolves the correct base path from NavigationManager.BaseUri at request time:

1<head>
2    <BasePath />
3    <link rel="stylesheet" href="css/app.css" />
4</head>

This is particularly valuable for apps deployed under a subpath (e.g., /dashboard or /app).

SignalR ConfigureConnection for Interactive Server Components

You can now configure the underlying HttpConnectionDispatcherOptions for Blazor Server’s SignalR connection via a clean, type-safe API:

 1app.MapRazorComponents<App>()
 2    .AddInteractiveServerRenderMode(options =>
 3    {
 4        options.ConfigureConnection = dispatcherOptions =>
 5        {
 6            dispatcherOptions.CloseOnAuthenticationExpiration = true;
 7            dispatcherOptions.AllowStatefulReconnects = true;
 8            dispatcherOptions.ApplicationMaxBufferSize = 1024 * 1024;
 9        };
10    });

Previously, reaching these options required inspecting endpoint metadata or other workarounds.

IHostedService Support in Blazor WebAssembly

Blazor WebAssembly now supports IHostedService for background work in the browser, closing a longstanding parity gap with Blazor Server:

 1public class DataRefreshService : IHostedService
 2{
 3    private Timer? _timer;
 4
 5    public Task StartAsync(CancellationToken cancellationToken)
 6    {
 7        _timer = new Timer(_ => RefreshData(), null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
 8        return Task.CompletedTask;
 9    }
10
11    public Task StopAsync(CancellationToken cancellationToken)
12    {
13        _timer?.Dispose();
14        return Task.CompletedTask;
15    }
16
17    private void RefreshData() { /* ... */ }
18}
19
20// In Program.cs
21builder.Services.AddHostedService<DataRefreshService>();

Environment Variables in Blazor WebAssembly Configuration

Blazor WebAssembly apps can now read environment variables through IConfiguration, making it easier to deploy the same binary to different environments without rebuilding:

1var apiEndpoint = builder.Configuration["API_ENDPOINT"];
2var featureFlag = builder.Configuration["ENABLE_FEATURE_X"];

Environment variables are automatically merged alongside appsettings.json and other configuration sources.

OpenAPI Schema Support for Binary File Responses

The built-in OpenAPI document generation now correctly describes endpoints that return binary files, mapping FileContentResult to a schema with type: string, format: binary:

1app.MapPost("/export", () =>
2{
3    var content = GenerateReport();
4    return TypedResults.File(content);
5})
6.Produces<FileContentResult>(contentType: MediaTypeNames.Application.Octet);

This fix ensures your generated OpenAPI documents accurately describe file download endpoints, which is especially important when sharing APIs with client generators.

IOutputCachePolicyProvider

A new interface allows implementing dynamic output cache policy resolution. Instead of only registering named policies at startup, you can resolve policies at runtime from external sources - a database, a feature flag system, or per-tenant configuration:

1public interface IOutputCachePolicyProvider
2{
3    IReadOnlyList<IOutputCachePolicy> GetBasePolicies();
4    ValueTask<IOutputCachePolicy?> GetPolicyAsync(string policyName);
5}

Auto-Trust for Dev Certificates in WSL

Running dotnet dev-certs https --trust inside WSL now automatically installs and trusts the certificate in both the WSL environment and Windows, eliminating a common friction point for developers who use WSL for their day-to-day .NET work.

Unified Startup Options for Blazor Scripts

The blazor.server.js and blazor.webassembly.js scripts now accept the same nested options format as blazor.web.js, removing an inconsistency that caused confusion when copying configuration snippets between different Blazor hosting models.

InvokeVoidAsync() Analyzer (BL0010)

A new analyzer catches the common mistake of using InvokeAsync<object> when the JavaScript function returns nothing:

1// ⚠️ BL0010 - Use InvokeVoidAsync for functions that don't return a value
2await JSRuntime.InvokeAsync<object>("console.log", "Hello");
3
4// ✅ Correct
5await JSRuntime.InvokeVoidAsync("console.log", "Hello");

Getting Started

To try .NET 11 Preview 1 today, install the SDK from the official download page:

Download .NET 11 Preview 1

If you are on Windows, the latest Visual Studio 2026 Insiders build supports .NET 11. You can also use VS Code with the C# Dev Kit extension.

For the full release notes, see the official .NET blog post and the component-specific notes for Runtime , Libraries , ASP.NET Core and the SDK .


Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization, or building new solutions from scratch - I'm here to help you succeed.

New Platforms
Modernization
Training & Consulting

Comments

Twitter Facebook LinkedIn WhatsApp