ASP.NET Core is a modern Web framework for .NET and includes everything you need to build beautiful Web UIs and powerful back-end services. Unlike other development platforms that require you to piece together a Web application from multiple frameworks, ASP.NET Core offers a complete and cohesive Web development solution (see Figure 1). With ASP.NET Core, you can build dynamic server-rendered UIs using MVC or Razor Pages. You can integrate ASP.NET Core with popular JavaScript frameworks or you can build rich interactive client Web UIs completely in .NET using Blazor. For services, you can use ASP.NET Core to build standards-based HTTP APIs, real-time services with SignalR, or high-performance back-end services with gRPC.

Under the hood, ASP.NET Core provides a flexible hosting model, high performance servers, and a rich set of built-in middleware to handle cross-cutting concerns like localization and security. No matter what kind of Web, server, or cloud app you're trying to build, ASP.NET Core offers a complete and fully integrated solution.

Figure 1: ASP.NET Core: A complete Web development solution
Figure 1: ASP.NET Core: A complete Web development solution

The next wave of new features and updates to ASP.NET Core is now available with .NET 6. .NET 6 is the latest major release of .NET, which now ships on a regular yearly cadence. .NET 6 is also a Long Term Support (LTS) release, which means that it will enjoy three full years of support.

.NET 6 includes improvements that cover a broad set of themes:

  • New developers: Makes it easier for new developers to get started with .NET
  • Client apps: Expands support for building cross-platform native client apps
  • Cloud native: Ensures that .NET has everything you need to run natively in the cloud
  • Enterprise and LTS: Ensures that enterprises relying on LTS releases have a smooth upgrade path
  • Ecosystem: Strengthens the .NET ecosystem
  • Inner-loop performance: Makes development with .NET faster and more productive
  • Meet developer expectations: Continues to deliver on the promises of the .NET platform

You can browse and dive into each of these .NET 6 themes on the https://themesof.net site and on GitHub.

ASP.NET Core has contributed new functionality and improvements to almost all the .NET 6 themes. To make it easier to find all the ASP.NET Core related work, there's an “ASP.NET Core in .NET 6 roadmap” issue linked to from the https://themesof.net site.

How did the ASP.NET team decide on these themes and enhancements? They were collected based on feedback from .NET community members, like you! Every suggestion, issue report, pull request, comment, and thumbs up has contributed to making .NET 6 a great release. Thank you for all your feedback and contributions!

There's lots that's new in in ASP.NET Core in .NET 6. Let's dive in and see what ASP.NET in .NET 6 has to offer.

Getting Started with ASP.NET Core in .NET 6

Getting started with ASP.NET Core in .NET 6 is easy. Just go to https://dot.net and install the .NET 6 SDK for your platform of choice. To create and run your first app, simply run the following commands:

dotnet new web
dotnet watch

You did it! Your browser should pop up and navigate to the running app.

You might see a warning in the browser that the development HTTPS certificate isn't trusted. To set up the ASP.NET Core development certificate for local development, run the following command and then restart the app:

dotnet dev-certs https --trust

.NET 6 is included with Visual Studio 2022, so if you have that installed, you're all set to go. Just make sure you've got the “ASP.NET and Web development” Visual Studio workload installed to enable the Web related tooling. If you're using an older version of Visual Studio, you'll need to upgrade to Visual Studio 2022. .NET 6 development isn't supported in older Visual Studio versions, so go ahead and treat yourself to the latest and greatest Visual Studio version.

To update an existing project to .NET 6, first update the target framework in your projects to net6.0 and then update any package references to the .NET 6 versions. .NET 6 is a highly compatible release with previous .NET versions, but double check the .NET 6 release notes for any breaking changes that might affect you. Most .NET 5 apps should work on .NET 6.

Now that you're all set up, let's check out all the new features.

Minimal APIs

Building your first ASP.NET Core app is easier than ever before with .NET 6. You can now build your first ASP.NET Core app with a single C# file and just a few lines of code.

Here's a complete ASP.NET Core app with .NET 6:

var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();

That's it! Run the app and you get a single HTTP endpoint that returns the text “Hello World!”

Building your first ASP.NET Core app is easier than ever before with .NET 6.

If you've been coding in C# for a while, you might be wondering how this code even compiles. Where's Program.Main? Modern C# no longer requires you to define Program.Main to get your program started. Instead, you can define top-level statements for your app's entry point, which reduces the amount of boilerplate code.

Where are the namespaces and using directives? You don't need to add using directives for the most common namespaces in .NET 6 because they're defined implicitly for you using the new C# 10 support for global using directives.

ASP.NET Core in .NET 6 takes full advantage of modern C#. All of the ASP.NET Core project templates have been updated to use the latest C# features including top-level statements, implicit global using directives, file-scoped namespace declarations, and nullability checking.

Microsoft has also introduced a new minimal hosting API for ASP.NET Core. The new WebApplication API gives you all the flexibility of a traditional ASP.NET Core Startup class, but with much less ceremony. The existing pattern of using a Startup class is, of course, still supported, but the new API is much more convenient.

You can add middleware directly to your WebApplication using the normal middleware extension methods just like you would in Startup.Configure:

var app = WebApplication.Create(args);
// Configure the HTTP request pipeline.app.UseStaticFiles();
app.Run();

To add services, create a WebApplicationBuilder and add services to the builder.Services property like you would in Startup.ConfigureServices:

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.builder.Servcies.AddSingleton<WeatherService>();
var app = builder.Build();
...
app.Run();

WebApplication sets up routing for you, so you're free to start adding endpoints right away. Improvements to routing in ASP.NET Core in .NET 6 makes building microservices and HTTP APIs trivial. You can create a minimal API by simply mapping a route to a method directly on your app. No need to define an entire API controller class (although doing so is still fully supported).

You've already seen that you can route to a method that returns a string to create minimal API that returns some plain text. If you return a complex object, it automatically gets serialized as JSON.

app.MapGet("/todos", () => new[] {
    new { Title = "Try .NET 6", IsDone = true},
    new { Title = "Eat veggies", IsDone = false},
});

With this minimal API, a GET request to /todos returns the following JSON response:

[
    {"title":"Try .NET 6","isDone":true},
    {"title":"Eat veggies","isDone":false}
]

You can also return an IResult instance from a route handler method. The Results static helper class provides many IResult implementations for common response types, like 404 Not Found or 201 Created.

Method parameters in route handlers can be bound to all sorts of useful stuff:

  • HttpContext
  • The HttpRequest or HttpResponse
  • The ClaimsPrincipal for the current user
  • Configured services

Simple type parameters get automatically bound to route value and query string parameters:

app.MapGet("/hello/{name}", (string name) => $"Hello {name}!");

Complex parameters get bound to JSON data in the request:

app.MapPost("/todos", async (Todo todo, TodoDbContext db) =>
{
    await db.Todos.AddAsync(todo);
    await db.SaveChangesAsync();
    return Results.Created($"/todo/{todo.Id}", todo);
});

Minimal APIs require only a minimal amount of code to implement, but they can be used to define APIs both big and small. Minimal APIs benefit from all the great functionality in ASP.NET Core, including support for authentication, authorization, CORS, and OpenAPI support. You can find a complete Todo API implementation in Listing 1 and tutorials for building minimal APIs in the ASP.NET Core docs. Also check out Brady Gaster's “Power Up your Power Apps with .NET 6 and Azure” elsewhere in this magazine for a complete end-to-end scenario based on minimal APIs.

Listing 1: Minimal Todo API

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("Todos") ??
    "Data Source=Todos.db";

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSqlite<TodoDbContext>(connectionString);
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() {
        Title = builder.Environment.ApplicationName, Version = "v1" });
});

var app = builder.Build();

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

app.MapFallback(() => Results.Redirect("/swagger"));

app.MapGet("/todos", async (TodoDbContext db) =>
{
    return await db.Todos.ToListAsync();
});

app.MapGet("/todos/{id}", async (TodoDbContext db, int id) =>
{
    return await db.Todos.FindAsync(id) is Todo todo ?
    Results.Ok(todo) :
    Results.NotFound();
});

app.MapPost("/todos", async (TodoDbContext db, Todo todo) =>
{
    await db.Todos.AddAsync(todo);
    await db.SaveChangesAsync();

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

app.MapPut("/todos/{id}",
    async (TodoDbContext db, int id, Todo todo) =>
    {
        if (id != todo.Id)
        {
            return Results.BadRequest();
        }

        if (!await db.Todos.AnyAsync(x => x.Id == id))
        {
            return Results.NotFound();
        }

        db.Update(todo);
        await db.SaveChangesAsync();

        return Results.Ok();
    });


app.MapDelete("/todos/{id}",
    async (TodoDbContext db, int id) =>
    {
        var todo = await db.Todos.FindAsync(id);
        if (todo is null)
        {
            return Results.NotFound();
        }

        db.Todos.Remove(todo);
        await db.SaveChangesAsync();

        return Results.Ok();
    });

app.Run();

More Developer Productivity

Building great Web apps requires rapid iteration. The faster you can make code changes and test them, the better. .NET 6 includes various improvements that make ASP.NET Core development faster, more iterative, and more fluid.

Faster Razor Compilation

.NET 6 includes optimizations across the .NET platform to speed up build times and improve app startup performance. For example, the Razor compiler was updated to use Roslyn Source Generators instead of its earlier two-phase approach, which makes building your MVC Views, Razor Pages, and Blazor components much faster. In many cases, compiling Razor files (.cshtml, .razor) is now over twice as fast as it was with .NET 5 (see Figure 2).

Figure 2: Faster Razor build times with .NET 6.
Figure 2: Faster Razor build times with .NET 6.

Hot Reload

.NET 6 adds support for .NET Hot Reload, which enables you to make changes to your app while it's running without having to restart it. The .NET tooling calculates the exact code delta that needs to be applied to the app based on your code changes and then applies the delta to the running app almost instantly. Because code changes are applied to the running app, any existing app state is preserved. .NET Hot Reload enables you to rapidly iterate on a specific part of the app with minimal disruption. .NET Hot Reload is designed to work with all the .NET 6 app models and it works great with all flavors of ASP.NET Core Web apps: MVC, Razor Pages, and Blazor.

.NET Hot reload works great with all flavors of ASP.NET Core Web apps.

.NET Hot Reload is enabled whenever you run an ASP.NET Core app using dotnet watch. Previously the dotnet watch command simply restarted your app and refreshed the browser whenever it detected a code file change. In .NET 6, dotnet watch first attempts to apply the changes to the running app using hot reload.

> dotnet watch
watch : Hot reload enabled.
For a list of supported edits, see https://aka.ms/dotnet/hot-reload.
Press "Ctrl + Shift + R" to restart.

If the hot reload succeeds, you see the result of the changes in the app almost immediately:

watch : File changed:
C:\BlazorApp\Pages\Index.razor.
watch : Hot reload of changes succeeded.

Hot Reload allows you to modify the markup for your views, pages, and components and see the visual impact in real time without losing any app state. You can quickly make updates to your Razor rendering logic, change the behavior of methods, and add new type members and attributes.

Not all changes can be safely applied at runtime. For example, you can't change the signature of a method. If a change can't be applied using Hot Reload, then the tooling gives you the option to restart the app to apply the change:

watch : File changed:
C:\BlazorApp\Pages\Counter.razor.
watch : Unable to apply hot reload because of
a rude edit. Rebuilding the app...
watch : Unable to handle changes using hot reload.
watch : Do you want to restart your app �
Yes (y) / No (n) / Always (a) / Never (v)?

You can also Hot Reload CSS changes into the browser without having to refresh the page. CSS Hot Reload will detect when CSS changes have been made and then apply them to the live DOM. CSS hot reload works with normal CSS files and with scoped CSS files.

Hot Reload is deeply integrated into Visual Studio 2022. You can easily apply changes to your running app while debugging and get great design-time feedback on what changes can be successfully applied. When making CSS changes, you can see the UI impact of your changes as you type with CSS auto sync. To learn all about the Visual Studio 2022 tooling for Hot Reload and all the other .NET productivity improvements be sure to check out Mika Dumont's article on “Visual Studio 2022 Productivity” elsewhere in this magazine.

MVC and Razor Pages Improvements

ASP.NET Core MVC has been the workhorse for ASP.NET Core Web apps for many years and it just keeps getting better. Here are some of the improvements coming to MVC and Razor Pages in .NET 6.

CSS Isolation for Pages and Views

CSS isolation was introduced in .NET 5 as a way to scope styles to a specific Blazor component. In .NET 6, the same functionality is enabled for MVC views and Razor pages.

View- and page-specific styles apply only to that view or page without polluting the global styles. Isolating styles to a specific page or view can make it easier to reason about the styles in your app and to avoid unintentional side effects as styles are added, updated, and composed from multiple sources.

You define view and page-specific styles using a .cshtml.css file that matches the name of the .cshtml file of the page or view. Any styles defined in the .cshtml.css file will only be applied to that specific view or page. ASP.NET Core achieves CSS isolation by rewriting the CSS selectors as part of the build so that they only match markup rendered by that view or page. ASP.NET Core then bundles together all the rewritten CSS files and makes the bundle available to the app as a static Web asset at the path [PROJECT NAME].styles.css.

IAsyncDisposable

You can now implement IAsyncDisposable on controllers, page models, and view components to asynchronously dispose of resources. By fully supporting async patterns, ASP.NET Core enables more efficient utilization of server resources.

Async Streaming

ASP.NET Core now supports using IAsyncEnumerable to asynchronously stream response data from controller actions. 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.

Support for async streaming in ASP.NET Core in .NET 6 can make using Entity Framework Core with ASP.NET Core more efficient by fully leveraging the IAsyncEnumerable implementations in Entity Framework Core for querying the database. For example, the following code no longer buffers the product data into memory before sending the response:

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

Better Integration with JavaScript Frameworks

ASP.NET Core works great as a back-end for JavaScript-based apps written with popular frontend frameworks. The .NET SDK includes project templates for using ASP.NET Core with Angular and React. In .NET 6, Microsoft has improved how ASP.NET Core integrates with front-end JavaScript frameworks using a common pattern that can be applied to using ASP.NET Core with other JavaScript frameworks as well.

Most modern JavaScript frameworks come with command-line tooling for creating new apps and running them during development. In production, the built JavaScript app runs on a production Web server, like an ASP.NET Core app. In previous .NET releases, ASP.NET Core projects proxied requests for the JavaScript app to the JavaScript development server during development to preserve the JavaScript development experience. To enable this setup, ASP.NET Core apps had to include Angular- and React-specific components. Adding support for additional JavaScript frameworks meant building and maintaining additional integration components in ASP.NET Core as the JavaScript ecosystem evolves.

In .NET 6, things are flipped around. Now, the browser is pointed at the JavaScript development server and configures the development server to proxy API requests to the ASP.NET Core back-end. Most modern JavaScript development servers have built-in proxying support for precisely this sort of setup. The proxy configuration lives in the project instead of in framework code, which makes it trivial to adapt to other front-end JavaScript frameworks.

Microsoft has updated the built-in Angular and React templates to the latest versions (Angular 12 and React 17) and reconfigured the templates to use this new proxying setup. This should simplify single-page app development with ASP.NET Core and establish a pattern that can be used by the .NET community to integrate ASP.NET Core with more JavaScript frameworks.

Blazor Improvements

Of course, why write your Web app in JavaScript when you can build the entire thing with just .NET? Blazor is a client-side Web UI framework included with ASP.NET Core that enables full stack Web development with .NET. .NET 6 includes support for both Blazor Server and Blazor WebAssembly apps, as well as new support for building hybrid native client apps using Blazor components. Regardless of how you decide to host your Blazor component, .NET 6 includes loads of new Blazor features that you can take advantage of.

WebAssembly Ahead-of-Time Compilation

Blazor WebAssembly now supports ahead-of-time (AOT) compilation, which compiles your .NET code directly to WebAssembly for a significant runtime performance boost.

Most Blazor WebAssemby apps today run using a .NET IL interpreter implemented in WebAssembly. Because the .NET code is interpreted, it generally runs much slower than it would on a normal .NET runtime. .NET WebAssembly AOT compilation addresses this runtime performance issue. The runtime performance improvement from WebAssembly AOT compilation can be quite dramatic for CPU intensive tasks. In some cases, code runs five to ten times faster than when interpreted!

.NET WebAssembly AOT compilation requires additional build tools that are installed as an optional .NET SDK workload. To install the .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

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

<RunAOTCompilation>true</RunAOTCompilation>

WebAssembly AOT compilation is performed when the project is published and generally takes a while: several minutes on small projects and potentially much longer for larger projects. Your app gets compiled to WebAssembly and bundled into the dotnet.wasm runtime bundle.

The download size of the published app with AOT compilation enabled is generally larger than the interpreted version, about two times bigger. .NET IL contains high-level instructions that must be expanded into native WebAssembly code. So, using WebAssembly AOT compilation trades off some load time performance for better runtime performance. Whether this tradeoff is worth it depends on your app. Many apps run interpreted just fine without the need for AOT compilation. Blazor WebAssembly apps that are particularly CPU-intensive will benefit the most from AOT compilation.

Smaller Download Size

Published Blazor WebAssembly apps are much smaller in .NET 6. In .NET 5, a minimal Blazor WebAssembly is about 1.7 MB when published. In .NET 6, it's now only 1.1 MB. This is thanks to some great new optimizations:

  • Smarter .NET IL trimming: NET IL trimming is much improved in .NET 6, and the core framework libraries are more trimming friendly.
  • Runtime relinking: In .NET 5, the .NET WebAssembly runtime was a fixed size. In .NET 6, the new .NET WebAssembly build tools support relinking the runtime to remove unused features. For example, if you use invariant globalization, tooling can remove a bunch of globalization code from the runtime.
  • Smaller JavaScript files: Thanks to some great contributions for the .NET community, the JavaScript files that ship as part of ASP.NET Core and Blazor are much better optimized for size.

Smaller download sizes mean that your published Blazor WebAssembly apps load faster, especially on slower networks. It also mitigates the size impact of WebAssembly AOT compilation because you're already starting with a smaller app.

Error Boundaries

Blazor error boundaries provide a convenient way to handle exceptions within a component hierarchy. To define an error boundary, wrap the desired content with the new ErrorBoundary component. The ErrorBoundary component normally renders its child content, but when an unhandled exception is thrown, the ErrorBoundary renders some error UI instead.

For example, you can add a Counter component wrapped in an error boundary to the home page of a default Blazor app like this:

<ErrorBoundary>
    <Counter />
</ErrorBoundary>

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

private void IncrementCount()
{
    if (currentCount >= 10)
    {
        throw new InvalidOperationException("I've run out of fingers!");
    }
    currentCount++;
}

Now, if you click the counter too much, an unhandled exception is thrown, which gets handled by the error boundary by rendering some default error UI, as shown in Figure 3.

Figure 3: Default error UI provided by an error boundary.
Figure 3: Default error UI provided by an error boundary.

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 are all defined using CSS in the app, so you're free to customize them. You can also change the default error content by setting the ErrorContent property.

<ErrorBoundary>
    <ChildContent>
        <Counter />
    </ChildContent>
    <ErrorContent>
        <p class="my-error">
            Something broke. Sorry!
        </p>
    </ErrorContent>
</ErrorBoundary>

Preserve Prerendered State

Blazor apps can be prerendered from the server to speed up the perceived load time of the app. The prerendered HTML can immediately be rendered while the app is set up for interactivity in the background. Blazor is deeply integrated into ASP.NET Core, so Blazor components can be prerendered from any MVC View or Razor Page using the built-in component tag helper. The ability to run Blazor components on the client or the server is one of Blazor's key strengths.

However, any state that was used during prerendering on the server is generally lost and must be recreated when the app is loaded on the client. If any state is set up asynchronously on the client, the UI may flicker as the prerendered UI is replaced with temporary placeholders and then fully rendered again.

To solve this problem, you need a way to persist state used during prerendering and transfer it to the client for reuse. That's what the new preserve-component-state tag helper is for.

<component type="typeof(App)" render-mode="ServerPrerendered" />
<persist-component-state />

In your app, you control what state you want the preserve-component-state tag helper to persist by registering a callback with the new PersistentComponentState service. Your registered callback is called when state is about to be persisted into the prerendered page so that you can add to the persisted state. You then retrieve any persisted state when initializing your components. Listing 2 shows an implementation of the FetchData component that uses persisted state for prerendering some weather data.

Listing 2: FetchData with persisted state from prerendering

@page "/fetchdata"

<PageTitle>Weather forecast</PageTitle>

@using BlazorApp1.Data
@inject WeatherForecastService ForecastService
@inject PersistentComponentState PersistentState

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;
    PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = PersistentState
        .RegisterOnPersisting(PersistForecasts);
        if (!PersistentState.TryTakeFromJson<WeatherForecast[]>
        ("fetchdata", out forecasts))
        {
            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
        }
    }

    private Task PersistForecasts()
    {
        PersistentState.PersistAsJson("fetchdata", forecasts);
        return Task.CompletedTask;
    }
}

By initializing your components with the same state used during prerendering, any expensive initialization steps only need to be executed once. The newly rendered UI also matches the prerendered UI, so no flicker occurs.

Infer Generic Component Types from Ancestors

When using a generic Blazor component, like a Grid<TItem> or ListView<TItem>, Blazor can typically infer the generic type parameters based on the parameters passed to the component, so you don't have to explicitly specify them. In more sophisticated components, you might have multiple generic components that get used together where the type parameters are intended to match, like Grid<TItem> and Column<TItem>. In these composite scenarios, generic type parameters often need to be specified explicitly on child components, like this:

<Grid Items="@GetSales">
    <Column TItem="SaleRecord" Name="Product" />
    <Column TItem="SaleRecord" Name="Sales" />
</Grid>

In .NET 6, Blazor can now infer generic type parameters from ancestor components. Ancestor components opt-in to this behavior by cascading a named type parameter to descendants using the [CascadingTypeParameter] attribute. This attribute allows generic type inference to work automatically with descendants that have a type parameter with the same name.

For example, you can define Grid.razor components that look like this:

@typeparam TItem
@attribute [CascadingTypeParameter(nameof(TItem))]
...
@code {
    [Parameter]
        public IEnumerable<TItem>? Items
            { get; set; }

    [Parameter]
        public RenderFragment? ChildContent
            { get; set; }
}

And you can define Column.razor components that look like this:

@typeparam TItem
...
@code {
    [Parameter]
    public string? Title { get; set; }
}

You can then use the Grid and Column components without specifying any type parameters, like this:

<Grid Items="@GetSales()">
    <Column Title="Product" />
    <Column Title="Sales" />
</Grid>

Faster JavaScript Interoperability for Binary Data

Blazor is all about writing client Web apps using .NET. But sometimes you still need to use a little JavaScript. Maybe you want to reuse an existing JavaScript library or customize some low-level browser behavior. Blazor supports JavaScript interoperability (JS interop), and .NET 6 improves how JS interop works with binary data.

Blazor no longer base64-encodes binary data when doing JS interop, which makes transferring binary data across the interop boundary much more efficient. Blazor also now supports streaming binary data through JS interop using the new IJSStreamReference interface.

var dataRef = await JS.InvokeAsync<IJSStreamReference>("getData");
using var dataRefStream = await dataRef.OpenReadStreamAsync();
// Write JS Stream to disk
var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");
using var outputFileStream = File.OpenWrite(outputPath);
await dataRefStream.CopyToAsync(outputFileStream);

Streaming data through JS interop is particularly useful in Blazor Server apps when you want to upload lots of data to the server. The InputFile component was updated to use these JS interop improvements to support much larger (>2GB) and faster file uploads.

Working with Query Strings

Blazor components can now receive parameters from the query string. To specify that a component parameter can come from the query string, apply the [SupplyParameterFromQuery] attribute in addition to the normal [Parameter] attribute:

[Parameter]
[SupplyParameterFromQuery]
public int? Page { get; set; }

You can also update the query string of the browser URL with new query string parameters using the new helper methods on NavigationManager:

var newUri = NavigationManager.GetUriWithQueryParameter("page", 3);
NavigationManager.NavigateTo(newUri);

Required Component Parameters

To indicate that a component parameter is required, apply the [EditorRequired] attribute:

[Parameter]
[EditorRequired]
public int IncrementAmount { get; set; }

If the user doesn't specify the required parameter when using the component, they'll get a build warning indicating that the parameter is missing. This isn't enforced at runtime, so you'll still need to deal with the possibility that the parameter wasn't set in your component implementation. But specifying required parameters does improve component usability.

Select Multiple

Blazor now provides the array of selected elements via ChangeEventArgs when handling the onchange event for a select element with the multiple attribute applied. Also, you can bind array values to the value attribute of a select element with the multiple attribute. The built-in InputSelect component also infers the multiple attribute when bound to an array.

DynamicComponent

DynamicComponent is a new built-in Blazor component that can be used to dynamically render a component specified by type.

<DynamicComponent Type="typeof(Counter)" />

Parameters can be passed to the rendered component using a dictionary:

<DynamicComponent Type="typeof(Counter)" Parameters="parameters" />
@code {
    Dictionary<string, object> parameters = new() { { "IncrementAmount", 10 } };
}

DynamicComponent is useful when you want to determine what component to render at runtime.

Render Root Components from JavaScript

What if you want to dynamically render a component at runtime from JavaScript? For example, you might want to add Blazor components to an existing JavaScript app. Top-level component in Blazor are called root components, and in earlier releases Blazor root components had to render when the app started up. In .NET 6, you can now dynamically render root components from JavaScript whenever you want.

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

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

You can 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 });

The ability to render root components from JavaScript enables all sorts of interesting scenarios, including building standards-based custom elements with Blazor. Experimental support for building custom elements is available using the Microsoft.AspNetCore.Components.CustomElements NuGet package. With this package installed, you can register root components as customer elements:

options.RootComponents.RegisterAsCustomElement<Counter>("my-counter");

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>

You can also now generate framework-specific JavaScript components from Blazor components for frameworks like Angular or React. The JavaScript component generation sample on GitHub shows how this can be done (see https://aka.ms/blazor-js-components). In this sample, you can attribute Blazor components to generate Angular or React component wrappers:

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

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

options.RootComponents.RegisterForAngular<Counter>();
options.RootComponents.RegisterForReact<Counter>();

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

// Angular
<counter [incrementAmount]="incrementAmount">
</counter>

// React
<Counter incrementAmount={incrementAmount}>
</Counter>

Hopefully this captures your imagination about what's now possible with Blazor components in JavaScript. Microsoft is excited to see what the community does with this feature!

Modify HTML Head Content

Blazor now has built-in support for modifying HTML head-element content from components, including setting the title and adding meta elements.

To specify the page's title from a component, use the new PageTitle component.

<PageTitle>Counter</PageTitle>

To add other content to the head element, use the new HeadContent component:

<HeadContent>
    <meta name="description" content="@content">
</HeadContent>

To enable the functionality provided by PageTitle and HeadContent, you need to add a HeadOutlet root component to your app that appends to the head element. In Blazor WebAssembly, you can register the HeadOutlet component like this:

builder.RootComponents.Add<HeadOutlet>("head::after");

In Blazor Server, the setup is slightly more involved. To support prerendering, the App root component needs to be rendered before the HeadOutlet. This is typically accomplished using MVC-style layouts. Check out the updated Blazor Server template in .NET 6 to see how to set this up.

.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). If you're not using .NET MAUI yet, you can also add Blazor components to your Windows Forms and WPF apps to start building UI in your Windows desktop apps that can be reused in .NET MAUI apps or on the Web. Check out Ed Charbeneau's article “Blazor Hybrid Web Apps with .NET MAUI” elsewhere in this magazine for all the details on how .NET MAUI and Blazor can be used together.

ASP.NET Core Runtime Improvements

Under the hood, ASP.NET Core apps are powered by versatile runtime that provides performance, reliability, and security. ASP.NET Core in .NET 6 includes many runtime improvements that will help to supercharge your Web apps.

Performance

Performance is an important part of every ASP.NET Core release. Better performance means better server resource utilization and reduced hosting costs. .NET 6 includes performance improvements at every level of the framework. ASP.NET Core in .NET 6 is the fastest Web framework Microsoft has ever shipped!

Here are some examples of the ASP.NET Core performance improvements in .NET 6.

  • Middleware request throughput is ~5% faster in .NET 6.
  • MVC on Linux is ~12%, thanks to faster logging.
  • The new minimal APIs offer twice the throughput of API controllers without compromising on features.
  • HTTPS connections use ~40% less memory, thanks to zero byte reads.
  • Protobuf serialization is ~20% faster with .NET 6.

ASP.NET Core in .NET 6 is the fastest Web framework Microsoft has ever shipped!

HTTP/3

HTTP/3 is the third and upcoming major version of HTTP. HTTP/3 has the same semantics as earlier HTTP versions, but introduces a new transport based on UDP called QUIC. HTTP/3 is still in the process of being standardized but has already gained significant adoption.

HTTP/3 and QUIC have several benefits compared to older HTTP versions:

  • Faster initial response time: HTTP/3 and QUIC requires fewer roundtrips to establish a connection, so the first request reaches the server faster.
  • Avoid head-of-line blocking. HTTP/2 multiplexes multiple requests on a TCP connection, so packet loss affects all requests on a given connection. QUIC provides native multiplexing, so lost packets only impact requests with lost data.
  • Transition between networks. HTTP/3 allows the app or Web browser to seamlessly continue when a network changes.

.NET 6 introduces preview support for HTTP/3 in Kestrel, the built-in ASP.NET Core Web server. HTTP/3 support in ASP.NET Core is a preview feature because the specification is being standardized. Kestrel also doesn't support the network transitions feature of HTTP/3 in .NET 6, but Microsoft will explore adding it in a future .NET release.

To try out HTTP/3 with Kestrel, first enable support for preview features in your project by setting the following property:

<EnablePreviewFeatures>True</EnablePreviewFeatures>

Then configure Kestrel to use HTTP/3:

using Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel((context, options) =>
{
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.UseHttps();
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
    });
});

Browsers are finicky about connecting to localhost over HTTP/3, so you'll need to deploy the server to a separate computer to try it out.

And All the Rest

There are many more ASP.NET Core features and improvements in .NET 6:

  • Support for Bootstrap 5
  • Collocate JavaScript modules with views, pages, and components (.cshtml.js, .razor.js)
  • Support for custom event arguments in Blazor
  • Support for native dependencies in Blazor WebAssembly
  • Blazor JavaScript initializers
  • WebSocket compression
  • HTTP and W3C logging
  • Shadow-copying with IIS
  • gRPC client support for load balancing and retries
  • Etc.

Be sure to check the .NET 6 release notes for the full list of everything that's new. You can also find additional details on all these new features in the ASP.NET Core docs: https://docs.asp.net.

I hope you've enjoyed learning about all the great new functionality in ASP.NET Core now available with .NET 6. On behalf of the ASP.NET team, we look forward to hearing about your experiences with this momentous release.