In this article, we will explore the new features and improvements that are part of the second preview of ASP.NET Core in .NET 8.
We will cover the new Blazor QuickGrid component, improved Blazor WebAssembly performance with the jiterpreter, new analyzers for minimal APIs, new APIs in ProblemDetails to support more resilient integrations, improvements to ObjectPool with the new IResettable interface, and performance improvements to the named pipes transport.
Blazor QuickGrid component
The Blazor QuickGrid component is now part of .NET 8! QuickGrid is a high performance grid component for displaying data in tabular form. QuickGrid is built to be a simple and convenient way to display your data, while still providing powerful features like sorting, filtering, paging, and virtualization.
To get started with QuickGrid:
- Add reference to the Microsoft.AspNetCore.Components.QuickGrid package.luaCopy
dotnet add package Microsoft.AspNetCore.Components.QuickGrid --prerelease
- Add the following Razor code to render a very simple grid
<QuickGrid Items="@people">
<PropertyColumn Property="@(p => p.PersonId)" Title="ID" Sortable="true" />
<PropertyColumn Property="@(p => p.Name)" Title="Name" Sortable="true" />
<PropertyColumn Property="@(p => p.BirthDate)" Title="Birth date" Format="yyyy-MM-dd" Sortable="true" />
</QuickGrid>
@code {
record Person(int PersonId, string Name, DateOnly BirthDate);
IQueryable<Person> people = new[]
{
new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
}.AsQueryable();
}
You can see various examples of QuickGrid in action on the QuickGrid demo site.
QuickGrid was originally introduced as an experimental package based on .NET 7. As part of bringing QuickGrid into .NET 8 we’ve made some changes and improvements to the API. To update an existing Blazor app that uses QuickGrid to the .NET 8 version, you may need to make the following adjustments:
- Rename the
Value
attribute on thePaginator
component toState
- Rename the
IsDefaultSort
attribute on columns toInitialSortDirection
and addIsDefaultSortColumn=true
to indicate the column should still be sorted by default. - Remove the
ResizableColumns
attribute onQuickGrid
. Built-in support for resizable columns was removed.
Improved Blazor WebAssembly performance with the jiterpreter
The jiterpreter is a new runtime feature in .NET 8 that enables partial JIT support in the .NET IL interpreter to achieve improved runtime performance.
Blazor WebAssembly apps are able to run .NET code in the browser thanks to a small .NET runtime implemented in WebAssembly that gets downloaded with the app. This runtime is a .NET IL interpreter that is fully functional, reasonably small in size, and allows for fast developer iteration, but lacks the runtime performance benefits of native code execution through just-in-time (JIT) compilation. JITing to WebAssembly requires creating new WebAssembly modules on the fly and instantiating them, which poses unique challenges for the runtime. Blazor WebAssembly apps can instead choose to compile ahead-of-time (AOT) to WebAssembly to improve runtime performance but at the expense of a much larger download size. Since some common .NET coding patterns are incompatible with AOT, the .NET IL interpreter is still needed as a fallback mechanism to maintain full functionality.
The jiterpreter optimizes execution of interpreter bytecodes by replacing them with tiny blobs of WebAssembly code. By leveraging the interpreter as a baseline, we’re able to optimize the most important parts of the app without having to handle more complex or obscure cases and without overly complicating the runtime. While the jiterpreter isn’t a full JIT implementation, it significantly improves runtime performance without the size and build time overhead of AOT. The jiterpreter helps when using AOT too by optimizing cases where the runtime has to fallback to the interpreter.
In .NET 8 Preview 2, the jiterpreter is automatically enabled for your Blazor WebAssembly apps. You don’t have to do anything extra to turn it on.
The jiterpreter can significantly speed up the performance of low-level operations. For example, the following micro benchmark test for Span<byte>.Reverse()
and String.Normalize()
ran 46.7% and 86.9% faster respectively:
These improvements add up and translate into better performance for higher layer features. In our JSON serialization tests, the jiterpreter is 40.8% faster:
New analyzer to detect multiple FromBody
attributes
In .NET 8 Preview 2, a new analyzer has been added to detect when more than one parameter is being resolved from the body in a minimal API. The analyzer helps developers avoid errors and warns when multiple [FromBody]
attributes are applied to parameters of a handler method.
For example, the following code will trigger the new analyzer warning:
// ASP0024
app.MapPost("/todos", ([FromBody] Todo todo, [FromBody] User user) => ...);
To address the analyzer warning, limit each handler to only one parameter resolved from the body, as shown in the following code:
app.MapPost("/todos", ([FromBody] Todo todo, ClaimsPrincipal user) => ...);
The new analyzer is an addition to the analyzers added in Preview 1 for API development and helps improve the development experience for developers working with minimal APIs.
New APIs in ProblemDetails
to support more resilient integrations
In .NET 7, the ProblemDetailsService
was introduced to improve the experience for generating error responses that comply with the ProblemDetails specification. In .NET 8 Preview 2, a new API called TryWriteAsync
has been added to the IProblemDetailsService
interface to make it easier for implementers to implement fallback behavior if the ProblemDetailsService
was not able to generate a ProblemDetail
.
Here is an example of how to use the new TryWriteAsync
API in user middleware:
var problemDetailsService = httpContext.RequestServices.GetService<IProblemDetailsService>();
if (problemDetailsService == null ||
!await problemDetailsService.TryWriteAsync(new() { HttpContext = httpContext }))
{
// Your fallback behavior, since problem details was not able to be written.
}
New IResettable interface in ObjectPool
Microsoft.Extensions.ObjectPool provides support for pooling object instances in memory, which is especially useful when working with expensive to allocate or initialize objects.
In .NET 8 Preview 2, ObjectPool is becoming even easier to use thanks to the addition of the IResettable interface. This interface defines a method called TryReset()
which can be used to reset an object back to its default state, making it available for reuse.
Here’s an example of how to use IResettable in conjunction with ObjectPool:
public class ReusableBuffer : IResettable
{
public byte[] Data { get; } = new byte[1024 * 1024]; // 1 MB
public bool TryReset()
{
Array.Clear(Data);
return true;
}
}
var bufferPool = ObjectPool.Create<ReusableBuffer>();
var buffer = bufferPool.Get();
try
{
await ProcessDataAsync(buffer.Data);
}
finally
{
bufferPool.Return(buffer); // Data is automatically reset
}
In the above code, ReusableBuffer
is an example of a type that can be reset to a default state, allowing it to be reused. With IResettable, bufferPool.Return(buffer)
will automatically reset Data
to its default state between uses.
Performance improvements to named pipes transport
In .NET 8 Preview 1, support for using named pipes with Kestrel was announced. In Preview 2, the performance of named pipe connections has been improved. Kestrel’s named pipe transport can now accept connections in parallel and reuse NamedPipeServerStream
instances.
The time it takes to create 100,000 connections has significantly improved with the latest changes:
- Before: 5.916 seconds
- After: 2.374 seconds
These improvements were suggested by the community, and thanks go to the folks at Unity for contributing to this area.