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" />
<p>@message</p>

@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 -->
<script>
    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')
            };
        }
    });
</script>

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.

Index.cshtml.css

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>
</Grid>

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>
</Grid>

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:

Grid.razor

@typeparam TItem
@attribute [CascadingTypeParameter(nameof(TItem))]

...

@code {
    [Parameter] public IEnumerable<TItem> Items { get; set; }
    [Parameter] public RenderFragment ChildContent { get; set; }
}

Column.razor

@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" />
</Grid>

@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.

_Host.cshtml

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

FetchData.razor

@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);
        }
        else
        {
            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!

25 comments

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

  • Michael Hamel 0

    I’m happy to see Blazor progressing so rapidly! But there’s still serious shortcomings in the documentation.

    When can we expect the syntax colorization of Razor code in the Blazor docs to be fixed? It’s been completely broken everywhere in the docs for at least a year, and I think since the first production release in September 2019. I don’t see any activity in the tickets below since October 2020:

    https://github.com/dotnet/AspNetCore.Docs/issues/17296

    (The one below is closed, but provides a good visual representation of what I’m referring to):

    https://github.com/dotnet/AspNetCore.Docs/issues/20278

    • Daniel RothMicrosoft employee 0

      Hi Michael. Yes, this has been a unfortunate long standing issue with Razor syntax coloring in the docs that we had hoped to get a resolution on sooner, but it’s taken longer than expected. We’re working on getting a short term resolution in place.

  • Stilgar Naib 0

    Why did you go for this manual data persistence? It seems to me that a declarative way would be better like marking the state to be persisted with some attributes. Or maybe the reverse marking the component as one that requires data persistence and opting out with attributes on fields that are not to be persisted. Supporting direct control over the persisted state is great and all but for maximum usefulness it seems to me that a more transparent declarative way would be needed.

    • Daniel RothMicrosoft employee 0

      Hi Stilgar. Thanks for this suggestion! Please go ahead and open a GitHub issue for this so that we can discuss it more broadly with the community. We’re starting with what we think is a minimal yet flexible solution, but we’re open to discussing improvements that would simplify development.

      • Stilgar Naib 0

        I have already opened an issue about this (I even think it is the first one) and suggested using attributes – https://github.com/dotnet/aspnetcore/issues/26794

        • Daniel RothMicrosoft employee 0

          Perfect! I’ve updated the issue title to reflect the declarative nature of the ask.

  • Todd Schavey 0

    Hey Dan,

    Why didn’t you guys fix…just kidding..

    You and the team frickin’ rock 🤘💪. I’m grateful for every increment the team releases. The output and coordination coming out of .NET teams these past few years is outstanding.

    Looking forward to full release 👍

    • Daniel RothMicrosoft employee 0

      Thanks Todd!. And if you ever do have some bugs you need us to fix you know where to find us on GitHub 😊.

  • saint4eva 0

    Hey Dan, you and your team are doing amazing. But, regarding the “Support for custom event arguments in Blazor”, why do I need to write JavaScript?

    • Howard Richards 0

      Hi Saint4eva – although Blazor allows you to write C# in the browser, not all browser capabilities are available directly via WASM so there are scenarios where you have to revert to JavaScript. So interop is still going to be required as we wait for WASM to catch up and implement more browser APIs.

  • Mike-E 0

    Any chance you can put more resources into Razor tooling? I am working with a sizable Blazor (server-side) project and it’s been nothing but grief over the past few months as I have added a good deal of .razor files. For instance, currently, the CPU will simply oscillate between 5-15% completely idle without any user interaction:

    https://developercommunity.visualstudio.com/t/Constant-Oscillation-of-CPU/1352436

    My rig feels like a cryptocurrency mining rig at the moment, but for Razor files. 😛

    EDIT: FWIW I just checked my solution which has 5 Razor-based projects, containing 153 razor files, some of which are quite involved. I have been thinking of breaking these up into “feature modules” (ala Prism, etc) to help out with the build times (which I am more than happy to see is addressed in .NET6!). Perhaps that might assist with the grief I am experiencing with the tooling. However, it would also be nice to know this scenario is accounted for going forward, just in case, etc.

    • Mike-E 0

      Another performance issue:

      https://developercommunity.visualstudio.com/t/razor-Development:-Basic-Development-Pe/1368587

      It is not uncommon for my CPU to throttle for many seconds at a time at 50-75% and stay there after I stop typing. I am pretty sure it’s due to the extensive nature of all the razor files that I have. Perhaps this scenario is not tested as much over there? Yet? 🙂 I am in a “real world” scenario here and it simply feels like this is not accounted for in your testing cycles. It would be incredibly valuable to know that you’re working towards a world where solutions featuring extensive Razor files are used in your testing cycles before deploying to us to destroy our machines with. 😆

      • Rogier van der Hee 0

        We have exactly the same issue. Nuking the .vs directory did help at some occasions, but still, the tooling is the biggest thing at the moment.

        • Daniel RothMicrosoft employee 0

          Hi Mike. The Visual Studio performance and reliability teams are looking into these issues, and should get back to you shortly. And yes, we are working to improve the experience with large projects and lots of Razor files. We also work with companies and users to share their real app code with us so that we can do performance testing with it. If that’s something you’d be interested in, let me know.

          • Rogier van der Hee 0

            I have the feeling it is related to usings and ViewImports. Within the scope of a project we put all our usings inside ViewImports, so our Razor components for that library are nice and short. But somehow it triggers a recursive CPU thingy

          • Mike-E 0

            Thank you for your reply and for your tremendous dedication to your product, Daniel. I will for sure keep the code offer in mind going forward, particularly if addressing these two outstanding issues does not solve the pain currently experienced. 👍

          • Mike-E 0

            Hi Daniel,

            Unfortunately, despite your assurances, it appears that the 20-30% razor file issue has been swept under the rug for a future sprint: https://github.com/dotnet/aspnetcore/issues/32118

            It would be incredibly valuable and appreciated to have better support for larger projects, so that those of us who spend the 10-12 hours per day in Visual Studio don’t have to deal with so much grief around the entire experience. 😛 It would seem that obvious performance issues such as this one would be given more of a priority, so I am a bit bewildered ATM on why this is getting shelved, especially with the provided dotTrace session that clearly shows the Razor engine generating a ton of GC time.

            Anyways, thank you for any further consideration.

          • Daniel RothMicrosoft employee 0

            Hi Mike-E. Sorry, that bot comment is a bit confusing, and is really meant for issues marked for further investigation, not for known bugs. We are planning to address this issue in .NET 6. The VS folks already looked at it and concluded it was a Razor compiler issue that we need to address.

          • Mike-E 0

            Ahhh awesome! Thank you so much, Daniel. I was actually diving back here to delete my comment as I felt my frustration got a bit too much of me. It’s been a real churn this past month especially. Not just with Visual Studio but also with ReSharper, having reported dozens of issues there as well. A battle on two fronts as it were.

            In any case, I am very glad to hear that it is a bug and that it will be addressed in .NET6. Thanks again for being so responsive and understanding. It is very much appreciated.

  • Gary WoodMicrosoft employee 0

    Daniel,

    Nice to find your blog and information on Blazor’s future. I’m using Blazor in WSD to create the portal for the new packaging system used for shipping our software updates. I’ve got 2 months experience under my belt and today was looking for internal sites and resources where to find out what components MS folks are building, issues and maybe a discussion alias to ask questions and listen to other’s issues. Nada, nothing found. How can we create an eco-system in Microsoft to share between developers? Do you have any ideas or who in Microsoft is incubating knowledge transfer about Blazor in Microsoft? On another thought I want to create Blazor components for filing bugs and for creating ICM incidents directly from my Blazor site, but I don’t want to re-invent something already created. I really wish there was a way to communication within Microsoft for internal issues.

    • Daniel RothMicrosoft employee 0

      Hi Gary. There are a bunch of teams using Blazor internally at Microsoft, but I don’t think anything formal has been setup yet for knowledge share. Blazor operates in the open on GitHub, which is where we encourage public discussion and issue tracking to happen. Let’s chat offline about what would best meet your needs.

  • John Kenedy 0

    I notice Blazor WebAssembly (.NET 5) has performance issues.

    Animations such as menu open/close will stutters, typing on a textbox that is bound with an event will cause lag or characters appearing one by one in a slow manner.

    I think Blazor in .NET 5 is not yet ready to be used for professional/serious businesses. One thing is probably because of interpreted WASM instead of a compiled WASM with shortcut of Mono WASM implementation.

    • Daniel RothMicrosoft employee 0

      Hi John. Basic menus and text editing should work fine. If you’re having performance issues with these basic scenarios, could you please open a GitHub issue with details on how to reproduce the problems you’re seeing? Be sure to include details on the code you are testing with and the hardware and browser version that you are testing on. Thanks!

    • Mark Radcliffe 0

      Hi John,

      It sounds like you are building a UI with a textbox inside it with an event handler on character change. This will trigger a UI refresh, and there are a couple of options for improving the performance. You can take control of the UI refresh loop by overriding ShouldRender, or Make a component for your text box that implements the logic locally that needs to occur on entry, that bubbles up an event when you want it.

      An example of this might be a debounced input, where on typing, you want to refresh some data, but only after 1000ms after the user last typed. If you don’t create this as a component or override the ShouldRender method you’ll be doing unnecessary rendering of the current .razor file the text input is in along with all sub components which I could understand causing a performance issue. You should look at the performance guidance in the documentation as there are a few things to understand with the way Blazor works to get the most out of it.

Feedback usabilla icon