ASP.NET Core updates in .NET 6 Preview 4

Daniel Roth

.NET 6 Preview 4 is now available and includes many great new improvements to ASP.NET Core.

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

  • Introducing minimal APIs
  • Async streaming
  • HTTP logging middleware
  • Use Kestrel for the default launch profile in new projects
  • IConnectionSocketFeature
  • Improved single-page app (SPA) templates
  • .NET Hot Reload updates
  • Generic type constraints in Razor components
  • Blazor error boundaries
  • Blazor WebAssembly ahead-of-time (AOT) compilation
  • .NET MAUI Blazor apps
  • Other performance improvements

Get started

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

If you’re on Windows using Visual Studio, we recommend installing the latest preview of Visual Studio 2019 16.11. If you’re on macOS, we recommend installing the latest preview of Visual Studio 2019 for Mac 8.10.

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 6 Preview 3 to .NET 6 Preview 4:

  • Update all Microsoft.AspNetCore.* package references to 6.0.0-preview.4.*.
  • Update all Microsoft.Extensions.* package references to 6.0.0-preview.4.*.

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

Introducing minimal APIs

In .NET 6, we are introducing minimal APIs for hosting and routing in web applications. This opens the doors for new developers building their first web application with .NET and to our existing customers who want to build small microservices and HTTP APIs. These streamlined APIs provide the benefits of ASP.NET MVC with less ceremony.

To try out creating a minimal API, create a new ASP.NET Core empty web app.

dotnet new web -o MinApi

With just a single file and a few lines of code, you now have a fully functioning HTTP API:

miniapi-preview

New routing APIs

The new routing APIs allow users to route to any type of method. These methods can use controller-like parameter binding, JSON formatting, and action result execution.

Before (using the existing Map APIs):

app.MapGet("/", async httpContext =>
{
    await httpContext.Response.WriteAsync("Hello World!");
});

After (using the new Map overloads):

app.MapGet("/", (Func<string>)(() => "Hello World!"));

C# 10 improvements

These APIs already take advantage of newer C# features, like top-level statements. With C# 10, which ships with .NET 6 later this year, the experience will get even better. For example, the explicit cast to (Func<string>) will no longer be necessary. The image below demonstrates what it will look like once all of the C# 10 features are implemented.

miniapi-functions

The developer goes from using classes and methods to using lambdas without giving up the ability to use attributes and other features available to MVC Controller actions.

New hosting APIs

The new empty web template is using the new hosting model introduced in .NET 6 Preview 4.

var app = WebApplication.Create(args);

app.MapGet("/", (Func<string>)(() => "Hello World!"));

app.Run();

You aren’t limited to just use the new routing APIs. Below is an example of an existing web app updated to use the new hosting model that configures services and adds middleware.

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Api", Version = "v1" });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Api v1"));
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

The new hosting API reduces the amount of boilerplate required to the configure and start any ASP.NET app.

Performance

These new routing APIs have far less overhead than controller-based APIs. Using the new routing APIs, ASP.NET Core is able to achieve ~800k RPS in the TechEmpower JSON benchmark vs ~500k RPS for MVC.

Tech-Empower-Benchmark

Async streaming

ASP.NET Core now supports async streaming from controller actions all the way down to the response JSON formatter. Returning an IAsyncEnumerable from an action no longer buffers the response content in memory before it gets sent. This helps reduce memory usage when returning large datasets that can be asynchronously enumerated.

Note that Entity Framework Core provides implementations of IAsyncEnumerable for querying the database. The improved support for IAsyncEnumerable in ASP.NET Core in .NET 6 can make using EF Core with ASP.NET Core more efficient. For example, the following code will no longer buffer the product data into memory before sending the response:

public IActionResult GetProducts()
{
    return Ok(dbContext.Products);
}

However, if you have setup EF Core to use lazy loading, this new behavior may result in errors due to concurrent query execution while the data is being enumerated. You can revert back to the previous behavior by buffering the data yourself:

public async Task<IActionResult> Products()
{
    return Ok(await dbContext.Products.ToListAsync());
}

See the related announcement for additional details about this change in behavior.

HTTP logging middleware

HTTP logging is a new built-in middleware that logs information about HTTP requests and HTTP responses including the headers and entire body.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpLogging();
}

HTTP logging provides logs of:

  • HTTP Request information
  • Common properties
  • Headers
  • Body
  • HTTP Response information

To configure the HTTP logging middleware, you can specify HttpLoggingOptions in your call to ConfigureServices():

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpLogging(logging =>
    {
        // Customize HTTP logging here.
        logging.LoggingFields = HttpLoggingFields.All;
        logging.RequestHeaders.Add("My-Request-Header");
        logging.ResponseHeaders.Add("My-Response-Header");
        logging.MediaTypeOptions.AddText("application/javascript");
        logging.RequestBodyLogLimit = 4096;
        logging.ResponseBodyLogLimit = 4096;
    });
}

This results in a new log with the HTTP request information in the Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware category.

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Protocol: HTTP/1.1
      Method: GET
      Scheme: https
      PathBase:
      Path: /
      QueryString:
      Connection: keep-alive
      Accept: */*
      Accept-Encoding: gzip, deflate, br
      Host: localhost:5001
      User-Agent: PostmanRuntime/7.26.5
      My-Request-Header: blogpost-sample
      Postman-Token: [Redacted]

For more information on how to use HTTP logging, take a look at the HTTP logging docs.

Use Kestrel for the default launch profile in new projects

Kestrel is default launch profile

We’ve changed the default launch profile from IIS Express to Kestrel for all new projects created in .NET 6 Preview 4. Starting Kestrel is significantly faster and results in a more responsive experience while developing your apps.

  IIS Express (ms) Kestrel (ms) % change
Debugging 4359 2772 36%
No debugging 1999 727 64%


IIS Express is still available as a launch profile for scenarios such as Windows Authentication or port sharing.

IConnectionSocketFeature

The IConnectionSocketFeature request feature gives you access to the underlying accept socket associated with the current request. It can be accessed via the FeatureCollection on HttpContext.

For example, the application below sets the LingerState property on the accepted socket:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.ConfigureEndpointDefaults(listenOptions => listenOptions.Use((connection, next) =>
    {
        var socketFeature = connection.Features.Get<IConnectionSocketFeature>();
        socketFeature.Socket.LingerState = new LingerOption(true, seconds: 10);
        return next();
    }));
});
var app = builder.Build();
app.MapGet("/", (Func<string>)(() => "Hello world"));
await app.RunAsync();

Improved single-page app (SPA) templates

We’ve updated the ASP.NET Core project templates for Angular and React to use a improved pattern for single-page apps that is more flexible and more closely aligns with common patterns for modern front-end web development.

Previously, the ASP.NET Core template for Angular and React used specialized middleware during development to launch the development server for the front-end framework and then proxy requests from ASP.NET Core to the development server. The logic for launching the front-end development server was specific to the command-line interface for the corresponding front-end framework. Supporting additional front-end frameworks using this pattern meant adding additional logic to ASP.NET Core.

The updated ASP.NET Core templates for Angular and React in .NET 6 flips this arrangement around and take advantage of the built-in proxying support in the development servers of most modern front-end frameworks. When the ASP.NET Core app is launched, the front-end development server is launched just as before, but the development server is configured to proxy requests to the backend ASP.NET Core process. All of the front-end specific configuration to setup proxying is part of the app, not ASP.NET Core. Setting up ASP.NET Core projects to work with other front-end frameworks is now straight-forward: simply setup the front-end development server for your framework of choice to proxy to the ASP.NET Core backend using the pattern established in the Angular and React templates.

The startup code for the ASP.NET Core app no longer needs any single-page app specific logic. The logic for starting the front-end development server during development is injecting into the app at runtime by the new Microsoft.AspNetCore.SpaProxy package. Fallback routing is handled using endpoint routing instead of SPA specific middleware.

Templates that follow this pattern can still be run as a single project in Visual Studio or using dotnet run from the command-line. When the app is published, the front-end code in the ClientApp folder is built and collected as before into the web root of the host ASP.NET Core app and served as static files. Scripts included in the template configure the front-end development server to use HTTPS using the ASP.NET Core development certificate.

.NET Hot Reload updates

The latest preview of Visual Studio has some initial support for .NET Hot Reload. You may have noticed the new Apply Code Changes button and debug option when debugging your app:

Apply Code Changes button

The Apply Code Changes button will update the running app with any code changes you’ve made in the editor even without having to save the file. Here’s an example of updating the Counter component to increment by two instead of one. Notice that the current count is not lost once the change has been applied.

.NET Hot Reload in Visual Studio with Blazor

.NET Hot Reload support in Visual Studio is still a work in progress, so there are a number of limitations when using it with ASP.NET Core apps:

  • You must run with the debugger attached to apply changes
  • Code changes can only be applied to C# files – changes to Razor files (.razor, .cshtml) don’t work yet
  • Applied changes don’t force the UI to update yet, so UI updates will need to be triggered manually
  • Changes can’t be applied to Blazor WebAssembly apps yet

All of these limitations are being worked and will be addressed in future Visual Studio updates. Stay tuned!

If you’re using .NET Hot Reload via dotnet watch, changes will now be applied to ASP.NET Core hosted Blazor WebAssembly apps. Changes will also be reapplied to your Blazor WebAssembly app if you refresh the browser.

To learn more about .NET Hot Reload you can get all the details in our blog post: Introducing .NET Hot Reload

Generic type constraints in Razor

When defining generic type parameters in Razor using the @typeparam directive, you can now specify generic type constraints using the standard C# syntax:

@typeparam TEntity where TEntity : IEntity

Blazor error boundaries

Blazor error boundaries provide a convenient way to handle exceptions within the component hierarchy. To define an error boundary, use the new ErrorBoundary component to wrap some existing content. The ErrorBoundary component will render its child content as long as everything is running smoothly. If an unhandled exception is thrown, the ErrorBoundary renders some error UI.

For example, we can add an error boundary around the body content of the layout of the default Blazor app like this:

<div class="main">
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank" rel="noopener">About</a>
    </div>

    <div class="content px-4">
        <ErrorBoundary>
            @Body
        </ErrorBoundary>
    </div>
</div>

The app continues to function as before but now our error boundary will handle unhandled exceptions. For example, we can update the Counter component to throw an exception if the count gets too big.

private void IncrementCount()
{
    currentCount++;
    if (currentCount > 10)
    {
        throw new InvalidOperationException("Current count is too big!");
    }
}

Now if we click the counter too much, an unhandled exception is thrown, which gets handled by our error boundary by rendering some default error UI:

Blazor error boundary default UI

By default, the ErrorBoundary component renders an empty div with a blazor-error-boundary CSS class for its error content. The colors, text, and icon for this default UI all defined using CSS in the app, so you are free to customize them. You can also change the default error content by setting the ErrorContent property.

<ErrorBoundary>
    <ChildContent>
        @Body
    </ChildContent>
    <ErrorContent>
        <p class="my-error">Nothing to see here right now. Sorry!</p>
    </ErrorContent>
</ErrorBoundary>

Because we defined the error boundary in the layout, once an exception is thrown we now only see the error content regardless of which page we navigate to. It’s generally best to scope error boundaries more narrowly than this, but we can choose to reset the error boundary to a non-error state on subsequent page navigations by calling the Recover method on the error boundary.

...
<ErrorBoundary @ref="errorBoundary">
    @Body
</ErrorBoundary>
...

@code {
    ErrorBoundary errorBoundary;

    protected override void OnParametersSet()
    {
        // On each page navigation, reset any error state
        errorBoundary?.Recover();
    }
}

Blazor WebAssembly ahead-of-time (AOT) compilation

Blazor WebAssembly now supports ahead-of-time (AOT) compilation, where you can compile your .NET code directly to WebAssembly for a significant runtime performance improvement. Blazor WebAssemby apps today run using a .NET IL interpreter implemented in WebAssembly. Because the .NET code is interpreted, typically this means that .NET code running on WebAssembly runs much slower than it would on a normal .NET runtime. .NET WebAssembly AOT compilation addresses this performance issue by compiling your .NET code directly to WebAssembly.

The performance improvement from AOT compiling your Blazor WebAssembly app can be quite dramatic for CPU intensive tasks. For example, the clip below shows a comparison of doing some basic image editing using the same Blazor WebAssembly app, first using the interpreter and then AOT compiled. The AOT compiled version runs over five times faster!

Picture Fixer AOT

You can check out the code for this PictureFixer on GitHub.

.NET WebAssembly AOT compilation requires an additional build tools that must be installed as an optional .NET SDK workload in order to use. To install the .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install microsoft-net-sdk-blazorwebassembly-aot

To enable WebAssembly AOT compilation in your Blazor WebAssembly project, add the following property to your project file:

<RunAOTCompilation>true</RunAOTCompilation>

To then AOT compile your app to WebAssembly, publish the app. Publishing using the Release configuration will ensure the .NET IL linking is also run to reduce the size of the published app:

dotnet publish -c Release

WebAssembly AOT compilation is only performed when the the project is published. It isn’t used when the project is run during development. This is because WebAssembly AOT compilation can take a while: several minutes on small projects and potentially much longer for larger projects. Speeding up the build time for WebAssembly AOT compilation is something that we are working on.

The size of an AOT compiled Blazor WebAssembly app is generally larger than the if the app was left as .NET IL. In our testing, most AOT compiled Blazor WebAssembly apps are about 2x larger, although it depends on the specific app. This means that using WebAssembly AOT compilation trades off load time performance for runtime performance. Whether this tradeoff is worth it will depend on your app. Blazor WebAssembly apps that are particularly CPU intensive will benefit the most from AOT compilation.

.NET MAUI Blazor apps

Blazor enables building client-side web UI with .NET, but sometimes you need more than what the web platform offers. Sometimes you need full access to the native capabilities of the device. You can now host Blazor components in .NET MAUI apps to build cross-platform native apps using web UI. The components run natively in the .NET process and render web UI to an embedded web view control using a local interop channel. This hybrid approach gives you the best of native and the web. Your components can access native functionality through the .NET platform, and they render standard web UI. .NET MAUI Blazor apps can run anywhere .NET MAUI can (Windows, Mac, iOS, and Android) although our primary focus for .NET 6 is on desktop scenarios.

To create a .NET MAUI Blazor app, you first need to set up .NET MAUI on your development machine. The easiest way to do that is using the maui-check tool.

To install the maui-check tool, run:

dotnet tool install -g Redth.Net.Maui.Check

Then run maui-check to acquire the .NET MAUI tooling and dependencies.

Add the following NuGet feed to get access to the .NET MAUI packages:

dotnet nuget add source -n maui-preview https://aka.ms/maui-preview/index.json

For additional information about getting started with .NET MAUI, refer to the wiki documentation on GitHub.

Once everything is installed, create a .NET MAUI Blazor app using the new project template:

dotnet new maui-blazor -o MauiBlazorApp

You can also create a .NET MAUI Blazor app using Visual Studio:

New .NET MAUI Blazor app

.NET MAUI Blazor apps are .NET MAUI apps that use a BlazorWebView control to render Blazor components to an embedded web view. The app code and logic lives in the MauiApp project, which is set up to multi-target Android, iOS, and Mac Catalyst. The MauiApp.WinUI3 project is used to build for Windows, and the MauiApp.WinUI3 (Package) project is used to generate an MSIX package for Windows. Eventually we expect to merge the Windows support into the main app project, but for now these separate projects are necessary.

The BlazorWebView control is set up for you in MainPage.xaml in the MauiApp project:

<b:BlazorWebView HostPage="wwwroot/index.html">
    <b:BlazorWebView.RootComponents>
        <b:RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
    </b:BlazorWebView.RootComponents>
</b:BlazorWebView>

The root Blazor component for the app is in Main.razor. The rest of the Blazor components are in the Pages and Shared directories. Notice that these components are the same ones used in the default Blazor template. You can use existing Blazor components in your app unchanged either by moving the code into the app, or by referencing an existing class library or package that contains the components. Static web assets for the app are in the wwwroot folder.

Windows

To run the app for Windows, you’ll need to build and run using Visual Studio.

Select the MauiBlazorApp.WinUI3 (Package) project as your startup project

Select WinUI startup project

Also select x64 for the target platform.

Select x64 target platform

You can then hit F5 or Ctrl+F5 to run the app as a native Windows desktop app using WinUI.

.NET MAUI Blazor app running on Windows

Android

To run the app on Android, first start the Android emulator using the Android SDK or the Android Device Manager.

Then run the app from the CLI using the following command:

dotnet build MauiBlazorApp -t:Run -f net6.0-android

To run on Android from Visual Studio, select the MauiBlazorApp project as the startup project

Select startup project

Then select net6.0-android as the target framework in the Run button drop-down menu.

Select Android target framework

You can then hit F5 or Ctrl+F5 to run the app using the Android emulator.

Android

iOS and Mac Catalyst

To run the app for iOS or Mac Catalyst, you’ll need to use a macOS development environment running Big Sur. You can’t currently run the app for iOS or Mac Catalyst from a Windows development environment, although we do expect that .NET MAUI will support running iOS apps using a connected Mac build agent or on a connected device using Hot Restart.

To run the app for iOS and Mac Catalyst, use the following commands:

dotnet build MauiBlazorApp -t:Run -f net6.0-ios
dotnet build MauiBlazorApp -t:Run -f net6.0-maccatalyst

iOS and Mac Catalyst

There are some known limitations with .NET MAUI Blazor apps in this release:

  • Component scoped CSS files (.razor.css) are not working yet in the main .NET MAUI project. This will get fixed in a future update.

Learn more about what’s new in .NET MAUI in .NET 6 Preview 4.

Other performance improvements

As part of continually improving the performance of ASP.NET Core, we made a whole host changes to reduce allocations and improve performance across the stack:

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!

51 comments

Discussion is closed. Login to edit/delete existing comments.

  • zephania Eliah 0

    Hellow Daniel ,
    Thanks for the big effort you and your team have put in making blazor a good product and we are interested in the direction blazor is taking especially with maui one it enables a lot of posiblities we didnt have before. But in our testing we come to face one huddle that we are not sure how to handle it gracefully, Blazor maui currently doesnt support IJSRuntime or may be we fall short on how to enable it. Can you help us how we can use IJSRuntime in blazor Maui app as the app we are trying to port heavily depending on some Javascript for ui staffs. if there is a nuget package we can use you may point us to…
    thanks for the efforts you made in making blazor great.

  • kartik poddar 0

    Hi Danial roth,

    i have tried this new BlazorWeather Maui app but when i have created with new app windows app appear blue screen only but it should be appear blazor component .

    android app and web is working fine.

    please suggest what i need to do.

    mainpage.xml file

    <b:BlazorWebView VerticalOptions="FillAndExpand" HostPage="wwwroot/index.html">
        <b:BlazorWebView.RootComponents>
            <b:RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
        </b:BlazorWebView.RootComponents>
    </b:BlazorWebView>
    

  • Prince Owusu 0

    Soon, We’re not going to need controllers any more thanks to the C# 10 improvements and the idea of adapting to the ways of setting up node servers. Sometimes finding the right name for a controller and controller method can be very difficult.

Feedback usabilla icon