.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:
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.
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.
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
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:
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 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:
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!
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:
.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
Also select x64 for the target platform.
You can then hit F5 or Ctrl+F5 to run the app as a native Windows desktop app using WinUI.
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
Then select net6.0-android as the target framework in the Run button drop-down menu.
You can then hit F5 or Ctrl+F5 to run the app using the Android emulator.
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
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:
- Non-allocating app.Use() extension method
- There is a new overload of
app.Use
that requires passing thecontext
tonext()
, this saves two internal per-request allocations that are required when using the other overload.
- There is a new overload of
- Reduce allocations for cookies
- Use LoggerMessage.Define for HttpSysServer (community contribution)
- We’ve replaced the use of ILogger extension methods with calls to LoggerMessage.Define() in
HttpSysServer
- We’ve replaced the use of ILogger extension methods with calls to LoggerMessage.Define() in
- Reduce the per connection overhead in SocketConnection.
- Reduced the
SocketConnection
overhead by ~30%
- Reduced the
- Reduce allocations by removing logging delegates in generic types
- Faster access to commonly-used features
- Improved access to commonly-used features (
IHttpRequestFeature
,IHttpResponseFeature
,IHttpResponseBodyFeature
,IRouteValuesFeatures
, andIEndpointFeature
) by ~50%
- Improved access to commonly-used features (
- Use interned Header Names for known header not in the pre-allocated block
- For well known headers, we swap out the values with interned strings to avoid string duplication across multiple connections.
- Reuse HttpProtocol
CancellationTokenSource
in Kestrel- Use the new
TryReset()
method onCancellationTokenSource
to reuse tokens if they haven’t been canceled.
- Use the new
- Avoid closure allocation in
DefaultHubDispatcher.Invoke()
- Passed state to a local static function via parameters to avoid a closure allocation
- Allocate HubCallerClient once per connection (instead of every hub method call)
- Allocate StreamItem once per stream (instead of per stream-item allocation) in server-to-client streaming
- Implement and use an
AdaptiveCapacityDictionary
for more efficient access to dictionaries of up to 4 entries
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!
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.
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
<code>
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...
Hello Daniel
This is awesome release for us especially Blazor Maui as it will be easy to use local db like LiteDB to store data locally and sync later, one bottleneck we have faced while trying out is how to use js interop as IJSruntime is not part of Blazor Maui or if we are missing which nuget package we have to use to bring in IJSRuntime or what is the work arround if...
Hi Zephania. Using IJSRuntime to do JavaScript interop should still work in a .NET MAUI app. The JavaScript interop calls should get forwarded to the browser control. If you’re seeing issues with this, please open a GitHub issue with details on what steps you are following and the exact error you are seeing: https://github.com/dotnet/aspnetcore/issues/new.
I tried to publish the project with aot enabled, but got this error:
<code>
Thank you for letting us know! Please report this issue with details on how to reproduce it on GitHub at https://github.com/dotnet/runtime/issues/new.
Someone has reported the bug already.
Great stuff! How can I try out these new SPA templates?
And a bonus question, how would I implement a BFF in .NET 6 like this:
Currently I have the issue that my HomeController has an authorize and authorizeforscopes tag (azure ad login) and then redirects to (/home) which is the spa. But I want the spa to run on / but that is where the homecontroller intercepts the request to check for auth. So I can't...
To try out the new ASP.NET Core SPA templates, run either
dotnet new angular
ordotnet new react
. To see how to setup authentication and authorization, see https://docs.microsoft.com/aspnet/core/security/authentication/identity-api-authorization.I didn’t properly know about Fluent UI until I saw it in Build, 1st a useful comment from Scott H, then it appeared on your presentation, so that is very useful. A common set of controls that can be adapted for the appropriate environment has to be a really useful stack. The one thing that is missing are ‘vectorisable’ controls for the web! In fact, such a stack would work in any environment?
I followed all the steps to create an AOT application, but the browser is still downloading dll’s. is that expected? How can I check the application is compiled to WASM?
Hi Alvaro. Yes, you will still see dll’s downloaded with the app even when using WebAssembly AOT compilation. This is because in some situations we still fall back to the .NET IL interpreter for functionality that isn’t support with AOT.
Daniel,
Thanks to you and the whole team, for great work. I’m reading those blogs and every new one make me more happy to use .NET !
Congratulations Dan, this is excellent progress!
Couple of questions:
Thank you!
Hi Daniel, I will have a go at answering your questions.
1. You would expect to get more than 5x speed improvement with AOT to WASM (compared to interpreted) if you were doing CPU intensive work. For real world projects it will depend on where the bottlenecks are (rendering, interop with javascript libraries, etc).
2. The WASM format under AOT ends up larger than shipping a .NET IL DLL. They are working to...
Hi Daniel,
Could you please share with us the scenarios where you were expecting a larger performance boost from WebAssembly AOT? It would be easiest if you could share specific benchmark scenarios on GitHub that we can test: https://github.com/dotnet/aspnetcore/issues/new.
We are actively working on reducing the download size of Blazor WebAssembly apps. Published Blazor WebAssembly apps with .NET 6 are already about 10% smaller than the same app published with .NET 5. We expect further improvements (~5-10%)...