ASP.NET Core updates in .NET 6 Release Candidate 1

Daniel Roth

.NET 6 Release Candidate 1 (RC1) is now available and includes many great new improvements to ASP.NET Core.

Here’s what’s new in this preview release:

  • Render Blazor components from JavaScript
  • Blazor custom elements
  • Manipulate the query string from Blazor
  • .NET to JavaScript streaming
  • PageX and PageY in MouseEventArgs
  • Blazor templates updated to set page titles
  • Disabled long-polling transport for Blazor Server
  • Collocate JavaScript files with pages, views, and components
  • JavaScript initializers
  • Customize Blazor WebAssembly packaging
  • Template improvements
  • Minimal API updates
  • Support for Latin1 encoded request headers in HttpSysServer
  • Emit KestrelServerOptions via EventSource event
  • Add timestamps and PID to ASP.NET Core Module logs
  • New DiagnosticSource event for rejected HTTP requests
  • Create a ConnectionContext from an Accept Socket
  • Streamlined HTTP/3 setup
  • Upgrade to Duende Identity Server

Get started

To get started with ASP.NET Core in .NET 6 RC1, install the .NET 6 SDK.

If you’re on Windows using Visual Studio, install the latest preview of Visual Studio 2022. Support for .NET 6 in Visual Studio for Mac is coming soon, and is currently available as a private preview.

To get setup with .NET MAUI & Blazor for cross-platform native apps, see the latest instructions in the .NET MAUI getting started guide. Please also read this important update on .NET MAUI.

To install the .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

Alternative, use the Visual Studio Installer to enable the “.NET WebAssembly build tools” optional component in the “ASP.NET and web development” workload.

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 6 Preview 7 to .NET 6 RC1:

  • Update all Microsoft.AspNetCore.* package references to 6.0.0-rc.1.*.
  • Update all Microsoft.Extensions.* package references to 6.0.0-rc.1.*.

To upgrade a .NET MAUI Blazor app from .NET 6 Preview 7 to .NET 6 RC1 we recommend starting from a new .NET MAUI Blazor project created with the .NET 6 RC1 SDK and then copying code over from your original project.

See the full list of breaking changes in ASP.NET Core for .NET 6.

Render Blazor components from JavaScript

Blazor is great for building client-side web UI using just .NET, but what if you already have existing JavaScript apps that you need to maintain and evolve? How can you avoid having to build common components twice, in .NET and JavaScript?

In .NET 6, you can now dynamically render Blazor components from JavaScript. This capability enables you to integrate Blazor components with existing JavaScript apps.

To render a Blazor component from JavaScript, first register it as a root component for JavaScript rendering and assign it an identifier:

Blazor Server

builder.Services.AddServerSideBlazor(options =>
    options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");

Blazor WebAssembly

builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter");

Load Blazor into your JavaScript app (blazor.server.js or blazor.webassembly.js) and then render the component from JavaScript into a container element using the registered identifier, passing component parameters as needed:

let containerElement = document.getElementById('my-counter');
await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 });

Blazor custom elements

Experimental support is also now available for building custom elements with Blazor using the Microsoft.AspNetCore.Components.CustomElements NuGet package. Custom elements use standard HTML interfaces to implement custom HTML elements.

To create a custom element using Blazor, register a Blazor root component as custom elements like this:


You can then use this custom element with any other web framework you’d like. For example, here’s how you would use this Blazor counter custom element in a React app:

<my-counter increment-amount={incrementAmount}></my-counter>

See the Blazor Custom Elements sample project for a complete example of how to create custom elements with Blazor.

This feature is experimental because we’re still working out some of the details for how best to support custom elements with Blazor. We welcome your feedback on how well this particular approach meets your requirements.

Generate Angular and React components using Blazor

You can also now generate framework specific JavaScript components from Blazor components for frameworks like Angular or React. This capability isn’t included with .NET 6, but is enabled by the new support for rendering Blazor components from JavaScript. The JavaScript component generation sample on GitHub demonstrates how you can generate Angular and React components from Blazor components.

In this sample, you attribute Blazor components to generate Angular or React component wrappers:

@*Generate an Angular component*@
@attribute [GenerateAngular]

@*Generate an React component*@
@attribute [GenerateReact]

You then register the Blazor components as Angular or React components:


When the project gets built, it generates Angular and React components based on your Blazor components. You then use the generated Angular and React components like you would normally:

// Angular
<counter [incrementAmount]="incrementAmount"></counter>
// React
<Counter incrementAmount={incrementAmount}></Counter>

This sample isn’t a complete solution for generating Angular and React components from Blazor components, but we hope it demonstrates what’s possible. We welcome and encourage community efforts to build on this functionality more fully. We’re excited to see what the community does with this feature!

Manipulate the query string from Blazor

New GetUriWithQueryParameter and GetUriWithQueryParameters extension methods on NavigationManager facilitate updating the query string of the browser URL.

To add or update a single query string parameter:

// Create a new URI based on the current address
// with the specified query string parameter added or updated.
var newUri = NavigationManager.GetUriWithQueryParameter("page", 3);

// Navigate to the new URI with the updated query string

Use GetUriWithQueryParameters to add, update, or remove multiple parameters based on a dictionary of parameter values (a null value removes the parameter).

.NET to JavaScript streaming

Blazor now supports streaming data from .NET to JavaScript. A .NET stream can be passed to JavaScript as a DotNetStreamReference.

using var data = new System.IO.MemoryStream(new byte[100 * 1024]);
using var streamRef = new DotNetStreamReference(stream: data, leaveOpen: false);
await JS.InvokeVoidAsync("consumeStream", streamRef);

In JavaScript, the data stream can then be read as an array buffer or as a readable stream:

async function consumeStream(streamRef) {
    const data = await streamRef.arrayBuffer();    // ArrayBuffer
    // or
    const stream = await;       // ReadableStream

Additional details on this feature are available in the Blazor JavaScript interop docs.

PageX and PageY in MouseEventArgs

MouseEventArgs now has PageX and PageY properties corresponding to the standard pagex and pagey on the MouseEvent interface. Thank you TonyLugg for helping us fill this functional gap.

Blazor templates updated to set page title

The Blazor project templates have been updated to support updating the page title as the user navigates to different pages using the new PageTitle and HeadOutlet components.

In the Blazor WebAssembly template, the HeadOutlet component is added as a root component that appends to the HTML head tag. Each page in the template sets the title using the PageTitle component.

The Blazor Server template required a bit more refactoring in order to support modifying the head when prerendering. The _Host.cshtml page now has its own layout, _Layout.cshtml, that adds the HeadOutlet component to the HTML head using the component tag helper. This ensures that the HeadOutlet is rendered before any components that want to modify the head.

Disabled long-polling transport for Blazor Server

Prior to .NET 6, Blazor Server apps would fall back to long-polling when WebSockets weren’t available, which often led to a degraded user experience. In .NET 6 we’ve disabled the long-polling transport for Blazor Server apps by default so that it’s easier to know when WebSockets haven’t been correctly configured. If your Blazor Server app still requires support for long-polling, you can reenable it. See the related breaking change notification for details.

Collocate JavaScript files with pages, views, and components

You can now collocate a JavaScript file with pages, views, and components using the .cshtml.js and .razor.js conventions. This is a convenient way to organize your code when you have JavaScript code that is specific to a page, view, or component. These files are publicly addressable using the path to the file in the project (Pages/Index.cshtml.js or _content/{LIBRARY NAME}/Pages/Index.cshtml.js if the file is coming from a library).

For example, if you have a component implemented by Pages/Counter.razor, you can add a JavaScript module for that component at Pages/Counter.razor.js and then load it like this:

var module = await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/Counter.razor.js");

JavaScript initializers

JavaScript initializers provide a way to execute some logic before and after a Blazor app loads. This is useful for customizing how a Blazor app loads, initializing libraries before Blazor starts up, and configuring Blazor settings.

To define a JavaScript initializer, add a JavaScript module to the web root of your project named {LIBRARY NAME}.lib.module.js. Your module can export the following well-known functions:

  • beforeStart: Called before Blazor boots up on the .NET side. Used to customize the loading process, logging level, and other hosting model specific options.
    • In Blazor WebAssembly, beforeStart receives the Blazor WebAssembly options and any extensions added during publishing.
    • In Blazor Server, beforeStart receives the circuit start options.
    • In BlazorWebViews, no options are passed.
  • afterStarted: Called after Blazor is ready to receive calls from JavaScript. Used to initialize libraries by making .NET interop calls, registering custom elements, etc.
    • The Blazor instance is always passed to afterStarted as an argument.

A basic JavaScript initializer looks like this:


export function beforeStart(options) {
export function afterStarted(blazor) {

JavaScript initializers are detected as part of the build and then imported automatically in Blazor apps. This removes the need in many cases for manually adding script references when using Blazor libraries.

Customize Blazor WebAssembly packaging

In some environments, firewalls or other security software block the download of .NET assemblies, which prevents the execution of Blazor WebAssembly apps. To enable support for Blazor WebAssembly in these environments, we’ve made the publishing and loading process for Blazor WebAssembly apps extensible so that you can customize the packaging and loading of the app. We’ll share more details on how to do this in a future post.

Template improvements

Implicit usings

Based on feedback from Preview 7, changes to the implicit usings feature were made as part of this release, including the requirement to opt-in to implicit usings in the project file, rather than them being included by default based on the project targeting .NET 6. This will ensure existing projects being migrated to .NET 6 aren’t impacted by the implicit usings until the author is ready to enable the feature.

You can read more about this change in its breaking changes document.

Randomized port allocation

New ASP.NET Core projects will now have random ports assigned during project creation for use by the Kestrel web server, matching the existing behavior when using IIS Express. This helps to minimize the chance that new projects end up using ports that are already in use on the machine, which results in a failure of the app to start when it’s launched from Visual Studio, or via dotnet run.

A port from 5000-5300 will be selected for HTTP, and from 7000-7300 for HTTPS, at the time the project is created. As always, the ports used during development can be easily changed by editing the project’s launchSettings.json file. When the app is run after publishing, Kestrel still defaults to using ports 5000 and 5001 (for HTTP and HTTPS respectively) if not otherwise configured. You can read more about configuring Kestrel in the docs.

New logging defaults

New ASP.NET Core projects have a simplified logging configuration in their appsettings.json and appsettings.Development.json files.

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"

The net effect of this change is that log messages at the “Information” level from sources other than ASP.NET Core, will now be emitted by default. This includes messages related to relational database querying from Entity Framework Core, which are now clearly visible by default in new applications:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7297
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5131
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\MyName\source\repos\WebApplication45\WebApplication45
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (25ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [w].[Id], [w].[Name]
      FROM [Widget] AS [w]

Minimal API updates

Improved support for OpenAPI

In RC1, we improved support for OpenAPI by adding ways to define metadata on a minimal endpoint either imperatively via extensions methods or declaratively via attributes.

We added support for the following metadata:

  • WithName metadata maps the endpoint name to an operationId in generated OpenAPI documents.
    • Note the endpoint name is also used when using LinkGenerator to generate URL/links for endpoints
    • When the user does not specify the endpoint name using the WithName metadata, the operation id will default to the name of the function (SayHello) as shown in the following example:
string SayHello(string name) => $"Hello, {name}!";
app.MapGet("/hello/{name}", SayHello);
  • WithGroupName metadata maps the endpoint group name to the document name in generated OpenAPI documents (typically used for versioning, e.g. “v1”, “v2”)
  • ExcludeFromDescription metadata indicates that the API should be excluded/ignored from OpenAPI document generation
  • ProducesValidationProblem indicates that the endpoint will produce 4xx http status codes and error details with application/validationproblem+json content type
  • Produces<T>(...) metadata indicates what response types a method produces, where each response is the combination of:
    • One HTTP status code
    • One or more content types, e.g. “application/json”
    • An optional schema per content type


OpenAPI extension methods can be used to imperatively add the required metadata to the endpoint:

app.MapGet("/admin", () => "For admins only")

app.MapPost("/todos", async (Todo todo, TodoDb db) =>
        await db.SaveChangesAsync();

        return Results.CreatedAtRoute("GetTodoById", new { todo.Id }, todo);

In addition to extension methods, attributes can be used to add metadata declaratively on endpoints:

app.MapGet("/admin", AdminDetails);
app.MapPost("/todos", AddTodo);

[Authorized(Policy = "Admins")]
string AdminDetails()
    return "For admins only";

[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest, "application/problem+json")]
[ProducesResponseType(typeof(Todo), StatusCodes.Status201Created)]
async Task<IResult> AddTodo(Todo todo, TodoDb db)
    await db.SaveChangesAsync();

    return Results.Created($"/todos/{todo.Id}", todo);

Parameter Binding improvements

Allow optional parameters in endpoint actions

We introduced support to allow developers to specify if parameters in their minimal action are optional.

For example, we can designate name as an optional route parameter in the sample below:

app.MapGet("/sayHello/{name?}", (string? name) => $"Hello World from {name}");

This indicates that both of the following calls will succeed by returning 2xx status code since the name parameter is optional.
curl localhost:5000/sayHello/John and curl localhost:5000/sayHello.

The same behavior applies for request bodies. The following example will call the method with a null todo when there is no a request body sent in a post/put call:

app.MapPost("/todos", (Todo? todo) => () => { });

Route parameters, query parameters, and body parameters can all be designated as optional by using a nullability annotation or providing a default value. When a required parameter is not provided, the delegate will return a 400 status.

As part of the changes, we also improved error messages when parameters fail to bind.

Fix issue with struct support in parameter binding

In Preview 7, binding a struct parameter did not work and would throw an System.InvalidOperationException: The binary operator Equal is not defined for the types 'Contact' and 'System.Object'. In RC1, we fixed the issue.

Now it’s possible for developers to write the code below for a minimal API:

var app = WebApplication.Create(args);

app.MapPost("/api/contact", (Contact contact) => $"{contact.PhoneNumber} {contact.Email}");


record struct Contact(string PhoneNumber, string Email);

Developer Exception Page Middleware change

With RC1, the DeveloperExceptionPageMiddleware will now be registered as the first middleware if the current environment is development. This means when IWebHostEnvironment.IsDevelopment() is true. This removes the need to manually register the middleware by developers as shown in the example below.


var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

if (app.Environment.IsDevelopment())
app.MapGet("/", "Hello World");



var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", "Hello World");


Other Improvements in minimal APIs

Modifying the host configuration after the WebApplicationBuilder has been created is no longer supported. Instead, the Create and CreateBuilder calls now support taking a WebApplicationBuilderOptions which can be used to specify properties like the environment name, content root path, and so on.

var config = new WebApplicationOptions 
    Args = args,
    EnvironmentName = Environments.Staging,
    ContentRootPath = "www/"

var app = WebApplication.Create(options);

app.MapGet("/", (IHostEnvironment env) => env.EnvironmentName);


In addition to configuring hosting settings using the WebApplicationOptions class, minimal APIs now support customizing the host configuration using command-line args. For example, the following can be used to set the environment name for a running app.

$ dotnet run --environment=Development

To support a wider variety of middleware, minimal API apps now support multiple calls to the UseRouting method without overriding existing endpoints. This enables registering middleware like the Developer Exception page (which is enabled by default now) in the app.

Minimal APIs now support using MapFallback to define behavior for fallback routes in apps.

var app = WebApplication.Create(options);

app.MapFallback("/subroute", (string shareCode) => { ... };


Support for Latin1 encoded request headers in HttpSysServer

HttpSysServer is now capable of decoding request headers that are Latin1 encoded. To do so, you must the set the UseLatin1RequestHeaders property on HttpSysOptions to true.

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseHttpSys(o => o.UseLatin1RequestHeaders = true);

Emit KestrelServerOptions via EventSource event

The KestrelEventSource now emits a new event containing the JSON-serialized KestrelServerOptions when enabled (with verbosity EventLevel.LogAlways). This event makes it easier to reason about the server behavior when analyzing collected traces. Here’s an example of the event payload:

  "AllowSynchronousIO": false,
  "AddServerHeader": true,
  "AllowAlternateSchemes": false,
  "AllowResponseHeaderCompression": true,
  "EnableAltSvc": false,
  "IsDevCertLoaded": true,
  "RequestHeaderEncodingSelector": "default",
  "ResponseHeaderEncodingSelector": "default",
  "Limits": {
    "KeepAliveTimeout": "00:02:10",
    "MaxConcurrentConnections": null,
    "MaxConcurrentUpgradedConnections": null,
    "MaxRequestBodySize": 30000000,
    "MaxRequestBufferSize": 1048576,
    "MaxRequestHeaderCount": 100,
    "MaxRequestHeadersTotalSize": 32768,
    "MaxRequestLineSize": 8192,
    "MaxResponseBufferSize": 65536,
    "MinRequestBodyDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
    "MinResponseDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
    "RequestHeadersTimeout": "00:00:30",
    "Http2": {
      "MaxStreamsPerConnection": 100,
      "HeaderTableSize": 4096,
      "MaxFrameSize": 16384,
      "MaxRequestHeaderFieldSize": 16384,
      "InitialConnectionWindowSize": 131072,
      "InitialStreamWindowSize": 98304,
      "KeepAlivePingDelay": "10675199.02:48:05.4775807",
      "KeepAlivePingTimeout": "00:00:20"
    "Http3": {
      "HeaderTableSize": 0,
      "MaxRequestHeaderFieldSize": 16384
  "ListenOptions": [
      "Address": "",
      "IsTls": true,
      "Protocols": "Http1AndHttp2"
      "Address": "https://[::1]:7030",
      "IsTls": true,
      "Protocols": "Http1AndHttp2"
      "Address": "",
      "IsTls": false,
      "Protocols": "Http1AndHttp2"
      "Address": "http://[::1]:5030",
      "IsTls": false,
      "Protocols": "Http1AndHttp2"

Add timestamps and PID to ASP.NET Core Module logs

The ASP.NET Core Module (ANCM) enhanced diagnostic logs now include timestamps and PID of the process emitting the logs. This makes it easier to diagnose issues with overlapping process restarts in IIS when you may have multiple IIS worker processes running.

The resulting logs now resemble the sample output included below:

[2021-07-28T19:23:44.076Z, PID: 11020] [aspnetcorev2.dll] Initializing logs for 'C:\<path>\aspnetcorev2.dll'. Process Id: 11020. File Version: 16.0.21209.0. Description: IIS ASP.NET Core Module V2. Commit: 96475a2acdf50d7599ba8e96583fa73efbe27912.
[2021-07-28T19:23:44.079Z, PID: 11020] [aspnetcorev2.dll] Resolving hostfxr parameters for application: '.\InProcessWebSite.exe' arguments: '' path: 'C:\Temp\e86ac4e9ced24bb6bacf1a9415e70753\'
[2021-07-28T19:23:44.080Z, PID: 11020] [aspnetcorev2.dll] Known dotnet.exe location: ''

New DiagnosticSource event for rejected HTTP requests

Kestrel now emits a new DiagnosticSource event for HTTP requests rejected at the server layer. Prior to this change, there was no way to observe these rejected requests. The new DiagnosticSource event Microsoft.AspNetCore.Server.Kestrel.BadRequest now contains a IBadRequestExceptionFeature that can be used to introspect the reason for rejecting the request.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var diagnosticSource = app.Services.GetRequiredService<DiagnosticListener>();
using var badRequestListener = new BadRequestEventListener(diagnosticSource, (badRequestExceptionFeature) =>
    app.Logger.LogError(badRequestExceptionFeature.Error, "Bad request received");
app.MapGet("/", () => "Hello world");


class BadRequestEventListener : IObserver<KeyValuePair<string, object>>, IDisposable
    private readonly IDisposable _subscription;
    private readonly Action<IBadRequestExceptionFeature> _callback;

    public BadRequestEventListener(DiagnosticListener diagnosticListener, Action<IBadRequestExceptionFeature> callback)
        _subscription = diagnosticListener.Subscribe(this!, IsEnabled);
        _callback = callback;
    private static readonly Predicate<string> IsEnabled = (provider) => provider switch
        "Microsoft.AspNetCore.Server.Kestrel.BadRequest" => true,
        _ => false
    public void OnNext(KeyValuePair<string, object> pair)
        if (pair.Value is IFeatureCollection featureCollection)
            var badRequestFeature = featureCollection.Get<IBadRequestExceptionFeature>();

            if (badRequestFeature is not null)
    public void OnError(Exception error) { }
    public void OnCompleted() { }
    public virtual void Dispose() => _subscription.Dispose();

Create a ConnectionContext from an Accept Socket

The newly introduced SocketConnectionContextFactory now makes it possible to create a ConnectionContext from an already accepted socket. This makes it possible to build a custom Socket-based IConnectionListenerFactory without losing out on all the performance work and pooling happening in SocketConnection.

Look at this example of a custom IConnectionListenerFactory for an example of how to use this new API.

Streamlined HTTP/3 setup

RC1 introduces an easier setup experience for using HTTP/3 in Kestrel. All that’s needed is to configure Kestrel to use the proper protocol.

HTTP/3 can be enabled on all ports using ConfigureEndpointDefaults, or for an individual port, as in the sample below.

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, options) =>
    options.Listen(IPAddress.Any, 5001, listenOptions =>
        // Use HTTP/3
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;

HTTP/3 is not supported everywhere. See Use HTTP/3 with the ASP.NET Core Kestrel web server for information on getting started with HTTP/3 in Kestrel.

Upgrade to Duende Identity Server

Templates which use Identity Server have now be updated to use Duende Identity Server, as previously discussed in our announcement.

If you are extending the identity models and are updating existing projects you will need to update the namespaces in your code from IdentityServer4.IdentityServer to Duende.IdentityServer and follow their migration instructions.

Please note the license model for Duende Identity Server has changed to a reciprocal license, which may require license fees if you use it commercially in production. You can check the Duende license page for more details.

Give feedback

We hope you enjoy this preview release of ASP.NET Core in .NET 6. We’re eager to hear about your experiences with this release. Let us know what you think by filing issues on GitHub.

Thanks for trying out ASP.NET Core!