ASP.NET Core updates in .NET 6 Preview 2

Daniel Roth

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

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

  • Razor compiler updated to use source generators
  • Support for custom event arguments in Blazor
  • CSS isolation for MVC Views and Razor Pages
  • Infer component generic types from ancestor components
  • Preserve prerendered state in Blazor apps
  • SignalR – Nullable annotations

Get started

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

If you’re on Windows using Visual Studio, we recommend installing the latest preview of Visual Studio 2019 16.10. 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 1 to .NET 6 Preview 2:

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

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

Razor compiler updated to use source generators

We’ve updated the Razor compiler in this preview to be implemented using C# source generators. Source generators run during compilation and can inspect what is being compiled to produce additional files that are compiled along with the rest of the project. Using source generators simplifies the Razor compiler and significantly speeds up build times.

The following graph shows the build time improvements when using the new Razor compiler to build the default Blazor Server and MVC templates:

Razor build performance improvements

Support for custom event arguments in Blazor

Blazor’s support for custom events is now expanded to also support custom event arguments. This allows passing arbitrary data to .NET event handlers with custom events.

For example, you may want to receive clipboard paste events along with the text that the user has pasted. To do so, first declare a custom name for your event, and a .NET class that will hold the event arguments for this event, by adding the following classes to your project:

[EventHandler("oncustompaste", typeof(CustomPasteEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
    // This static class doesn't need to contain any members. It's just a place where we can put
    // [EventHandler] attributes to configure event types on the Razor compiler. This affects the
    // compiler output as well as code completions in the editor.

public class CustomPasteEventArgs : EventArgs
    // Data for these properties will be supplied by custom JavaScript logic
    public DateTime EventTimestamp { get; set; }
    public string PastedData { get; set; }

Once those are in place, you’ll get intellisense for a new event called @oncustompaste in your Razor components. For example, in Index.razor, you could use it as follows:

@page "/"

<p>Try pasting into the following text box:</p>
<input @oncustompaste="HandleCustomPaste" />

@code {
    string message;

    void HandleCustomPaste(CustomPasteEventArgs eventArgs)
        message = $"At {eventArgs.EventTimestamp.ToShortTimeString()}, you pasted: {eventArgs.PastedData}";

However, if you actually run the code now, the event will not fire. There’s one remaining step, which is adding some JavaScript code that actually supplies data for your new EventArgs subclass. In your index.html or _Host.cshtml file, add the following:

<!-- You should add this directly after the <script> tag for blazor.server.js or blazor.webassembly.js -->
    Blazor.registerCustomEventType('custompaste', {
        browserEventName: 'paste',
        createEventArgs: event => {
            // This example only deals with pasting text, but you could use arbitrary JavaScript APIs
            // to deal with users pasting other types of data, such as images
            return {
                eventTimestamp: new Date(),
                pastedData: event.clipboardData.getData('text')

This tells the browser that, whenever a native paste event occurs, it should also raise a custompaste event and supply the event arguments data using your custom logic. Note that the conventions around event names differ between .NET (where the event names are prefixed with on) and JavaScript (where they do not have any prefix).

CSS isolation for MVC Views and Razor Pages

CSS isolation is now supported with MVC views and Razor Pages just like it is with Blazor components. To add a view or page specific CSS file, simply add a .cshtml.css file matching the name of the .cshtml file.


h1 {
    color: red;

In your layout add a link to {PROJECT NAME}.styles.css to reference the bundled styles.

<link rel="stylesheet" href="MyWebApp.styles.css" />

The styles will then only be applied to their respective views and pages:

MVC CSS isolation

Infer component generic types from ancestor components

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 though, you might have multiple generic components that get used together where the type parameters are intended to match, like a Grid<TItem> and Column<TItem>. In these composite scenarios, generic type parameters would often need to be specified explicitly, like this:

<Grid Items="@people">
    <Column TItem="Person" Name="Full name">@context.FirstName @context.LastName</Column>
    <Column TItem="Person" Name="E-mail address">@context.Email</Column>

But what you really want to do is this:

<Grid Items="@people">
    <Column Name="Full name">@context.FirstName @context.LastName</Column>
    <Column Name="E-mail address">@context.Email</Column>

It was necessary to re-specify TItem on each <Column>, because each <Column> was treated as an independent component that had no other way to know what type of data it should work with.

In .NET 6 Preview 2, Blazor can now infer generic type parameters from ancestor components. Ancestor components must opt in to this behavior. An ancestor component can cascade a type parameter by name to descendants using the [CascadingTypeParameter] attribute. This attribute allows generic type inference to use the specified type parameter automatically with descendants that have a type parameter with the same name.

For example, you can define Grid and Column 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; }


@typeparam TItem


@code {
    [Parameter] public string Title { get; set; }

You can then use the Grid and Column components like this:

<Grid Items="@GetItems()">
    <Column Title="Product name" />
    <Column Title="Num sales" />

@code {
    IEnumerable<SaleRecord> GetItems() { ... }

Note: The Razor support in Visual Studio Code has not yet been updated to support this feature, so you may get incorrect errors even though the project correctly builds. This will be addressed in an upcoming tooling release.

Preserve prerendered state in Blazor apps

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 setup for interactivity in the background. However, any state that was used during prerendering is lost and must be recreated when the app is fully loaded. If any state is setup asynchronously, then the UI may flicker as the the prenrendered UI is replaced with temporary placeholders and then fully rendered again.

To solve this problem, we’ve added support for persisting state into the prerendered page using the new <persist-component-state /> tag helper. In your app, you decide what state you want to persist using the new ComponentApplicationState service. The ComponentApplicationState.OnPersisting event is fired when state is about to be persisted into the prerendered page. You can then retrieve any persisted state when initializing your components.

The example below shows how the weather forecasts in the default FetchData component can be persisted during prerendering and then retrieved to initialize the component in a Blazor Server app.


    <component type="typeof(App)" render-mode="ServerPrerendered" />
    @* Persist the component state after all component invocations *@
    <persist-component-state />


@page "/fetchdata"
@implements IDisposable
@inject ComponentApplicationState ApplicationState


@code {
    protected override async Task OnInitializedAsync()
        ApplicationState.OnPersisting += PersistForecasts;
        if (!ApplicationState.TryRedeemPersistedState("fetchdata", out var data))
            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
            var options = new JsonSerializerOptions
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                PropertyNameCaseInsensitive = true,
            forecasts = JsonSerializer.Deserialize<WeatherForecast[]>(data, options);

    private Task PersistForecasts()
        ApplicationState.PersistAsJson("fetchdata", forecasts);
        return Task.CompletedTask;

    void IDisposable.Dispose()
        ApplicationState.OnPersisting -= PersistForecasts;

Note: A TryRedeemFromJson<T> helper method is available with this preview release, but has a known issue that will addressed in a future update. To work around the issue, use TryRedeemPersistedState and manual JSON deserialization as show in the example above.

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

SignalR – Nullable annotations

The ASP.NET Core SignalR Client package has been annotated for nullability. This means that the C# compiler will provide appropriate feedback, when you enable nullability, based on your handling of nulls in SignalR APIs. The SignalR server has already been updated for nullability in .NET 5, but a few modifications were made in .NET 6. You can track ASP.NET Core support for nullable annotations at dotnet/aspnetcore #27389.

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!