ASP.NET Core updates in .NET 6 Preview 6

Daniel Roth

Daniel

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

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

  • Improved Blazor accessibility
  • Required Blazor component parameters
  • Efficient byte array transfers for JavaScript interop
  • Optional parameters for view component tag helpers
  • Angular template updated to Angular 12
  • OpenAPI support for minimal APIs
  • Inject services into minimal APIs without [FromServices] attribute
  • Configure the accept socket for Kestrel
  • IHttpActivityFeature
  • Long running activity tag for SignalR connections
  • WebSocket compression
  • SignalR WebSockets TestServer support
  • New OnCheckSlidingExpiration event for controlling cookie renewal
  • ClientCertificateMode.DelayCertificate

Get started

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

If you’re on Windows using Visual Studio, we recommend installing the latest preview of Visual Studio 2022. If you’re on macOS, we recommend installing the latest preview of Visual Studio 2019 for Mac 8.10.

To get setup with .NET MAUI & Blazor for cross-platform native apps, see the latest instructions in the .NET MAUI getting started guide. Be sure to also check out the Announcing .NET MAUI Preview 6 blog post for all the details on what’s new in .NET MAUI in this release.

To install the latest .NET WebAssembly tools for ahead-of-time (AOT) compilation and runtime relinking, run the following command from an elevated command prompt:

dotnet workload install microsoft-net-sdk-blazorwebassembly-aot

If you’ve installed the .NET WebAssembly tools workload previously, you can update it to .NET 6 Preview 5 by running the following command from an elevated command prompt:

dotnet workload update

Upgrade an existing project

To upgrade an existing ASP.NET Core app from .NET 6 Preview 5 to .NET 6 Preview 6:

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

To upgrade a .NET MAUI Blazor app from .NET 6 Preview 5 to .NET 6 Preview 6 we recommend starting from a new .NET MAUI Blazor project created with the .NET 6 Preview 5 SDK and then copying code over from your original project.

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

Improved Blazor accessibility

We made a number of changes to the default Blazor template to improve accessibility when using a screen reader:

  • We added role="alert" attributes to messages that should be announced when navigating to a page and removed role="alert" from the survey prompt component to deemphasize its content.
  • We updated the Counter component to add role="status" so that the current count is read as the counter gets updated.
  • We switched to using more semantic markup elements where appropriate, like main and article.
  • We swapped out the ul in the NavBar component for a nav so that its semantics are easier to identify and so it can be jumped to directly using common screen reader keyboard shortcuts.
  • We added a title to the NavBar toggle button so that it’s purpose is clearly announced.

We also added a new FocusOnNavigate component to Blazor, that sets the UI focus to an element based on a CSS selector after navigating from one page to another. You can see the FocusOnNavigate component at work in the App component in the default Blazor template:

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

When the Router navigates to a new page, the FocusOnNavigate component sets the focus on the top-level header for that page, which gets read by screen readers. This is a common strategy in single-page apps for making sure that page navigations are appropriately announced when using a screen reader.

We’re always looking for ways to to improve the accessibility of apps built with ASP.NET Core & Blazor. If you have ideas on how to further improve accessibility, you can share your suggestions with us on GitHub. You can also learn more about building accessible apps with .NET by checking out the recent ASP.NET Community Standup on Accessibility for Web Developers or by watching the recent Let’s Learn .NET: Accessibility event.

Required Blazor component parameters

To require that a Blazor component parameter be specified when using the component, use the new [EditorRequired] attribute:

SurveyPrompt.razor

[EditorRequired]
[Parameter]
public string Title { get; set; }

If the user tries to use the component without specifying the required parameter, they get a warning:

Required parameter

The [EditorRequired] attribute is enforced at design-time and as part of the build. It isn’t enforced at runtime and it doesn’t guarantee the the parameter value cannot be null.

Efficient byte array transfer in JavaScript interop

Blazor now has more efficient support for byte arrays when performing JavaScript interop. Previously, byte arrays sent to and from JavaScript were Base64 encoded so they could be serialized as JSON, which increased the transfer size and the CPU load. This encoding has now been optimized away in .NET 6. The new byte array optimization is transparent to the user when passing a Uint8Array from JavaScript to .NET.

When passing a byte[] from .NET to JavaScript, the bytes are now received as a Uint8Array instead of as a Base64 encoded string. Code that previously decoded the Base64 encoded string will need to be removed. See the related announcement for details on this breaking change.

Optional parameters for view components tag helpers

View component tag helpers now support optional parameters. If your view component has an optional parameter, you no longer need to specify a value for that parameter as a tag helper attribute.

So, if you have a view component with an optional parameter like this:

class MyViewComponent
{
    IViewComponentResult Invoke(bool showSomething = false) { ... }
}

then you can now invoke it as a tag helper without having to specify a value for the showSomething parameter:

<vc:my />

Angular template updated to Angular 12

The ASP.NET Core template for Angular now uses Angular 12.

OpenAPI support in minimal APIs

In .NET 6 preview 4, we announced minimal APIs for hosting and routing in web applications. We shared how one could develop an API in a single file with just a few lines of code. Our new streamlined APIs provide the benefits of ASP.NET with less ceremony.

Today we are excited that minimal APIs now have support for OpenAPI. With OpenAPI (Swagger) support, you can now easily set up Swagger UI to visualize and interact with minimal APIs.

Configure Swagger UI with minimal APIs

To try OpenAPI with minimal APIs, create a new ASP.NET Core empty web app using the .NET CLI dotnet new web -o MyApi or select “ASP.NET Core Empty” in Visual Studio.

Add Swagger UI to your application

Add the Swashbuckle.AspNetCore package to your application

dotnet add package Swashbuckle.AspNetCore

Open Program.cs and update the following configurations to set up Swagger in you application; you will need to update the following:

Dependency Injection: Add the Swagger document generator AddSwaggerGen to builder.services.

builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Description = "Docs for my API", Version = "v1" });
});

Middleware: Call app.UseSwagger and app.UseSwaggerUI to add the Swagger document and UI middleware to your application.

app.UseSwagger();

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});

Run your application and navigate to this URL https://localhost:5001/swagger.

swagger-P6

Now that Swagger UI is setup, you can visualize and interact with your API.

The screenshot below is for a more fully featured API that operates with a SQLite database. We will see more of these features in .NET 6 Preview 7.

swagger-P6-HTTPMethods

Inject services into minimal APIs without [FromServices] attribute

This preview also allows developers to inject services to their routing handlers without the need for the [FromServices] attribute.

Before: Code with [FromServices] attribute

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

After: Code without [FromServices] attribute

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

We’ve added a new capability to the dependency injection container called IServiceProviderIsService. This capability allows developers to query the container to determine if a type is resolvable. The new routing APIs use this capability to determine if a parameter can be bound from services.

The code below shows how you can use the IServiceProviderIsService capability to detect if MyService and NotAService are considered services by the dependency injection container:

var serviceProvider = new ServiceCollection()
    .AddSingleton<MyService>()
    .BuildServiceProvider();

var detector = serviceProvider.GetService<IServiceProviderIsService>();
if (detector is not null)
{
    Console.WriteLine($"MyService is a service = {detector.IsService(typeof(MyService))}");
    Console.WriteLine($"NotAService is a service = {detector.IsService(typeof(NotAService))}");
}

class MyService { }
class NotAService { }

Configure the accept socket for Kestrel

We’ve added a callback on SocketTransportOptions that allows you to control creation of the accept socket by creating your own socket and binding to the specified endpoint. If you only want to mutate the socket after it’s bound, you can call the CreateDefaultBoundListenSocket static helper method on SocketTransportOptions.

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseSockets(options =>
{
    options.CreateBoundListenSocket = (endpoint) =>
    {
        var socket = SocketTransportOptions.CreateDefaultBoundListenSocket(endpoint);
        // Modify listen socket
        return socket;
    };
});

IHttpActivityFeature

Hosting creates an Activity every request when there are diagnostic listeners present. There is now a feature to allow middleware to access this activity so additional information can be included during request processing.

app.Run(context =>
{
    var activity = context.Features.Get<IHttpActivityFeature>()?.Activity;
    if (activity is not null)
    {
        activity.AddTag("some_info", "true");
    }
});

Long running activity tag for SignalR connections

SignalR uses the new IHttpActivityFeature to add an “http.long_running” tag to the request activity. This will be used by APM services like Azure Monitor Application Insights to filter SignalR requests from creating long running request alerts.

WebSocket compression

You can now optionally accept WebSocket connections that use compression. Compression is off by default because enabling compression over encrypted connections can make the app subject to CRIME/BREACH attacks. It should only be enabled if you know that sensitive information isn’t being sent or if you turn off compression for the messages that contain sensitive information with the System.Net.WebSockets.WebSocketMessageFlags.DisableCompression flag in the SendAsync overload. Note: Clients would also need to disable compression when sending sensitive information which is not an option when using WebSockets in the browser.

app.Run(context =>
{
    if (context.WebSockets.IsWebSocketRequest)
    {
        using var websocket = await context.WebSockets.AcceptWebSocketAsync(
            new WebSocketAcceptContext() { DangerousEnableCompression = true });
        await websocket.SendAsync(...);
        await websocket.ReceiveAsync(...);
        await websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default);
    }
});

SignalR WebSockets TestServer Support

We have added WebSocketFactory to the options when creating a .NET SignalR connection. This option can be used with TestServer to allow testing with the WebSocket transport.

WebApplicationFactory<Startup> factory;
var connection = new HubConnectionBuilder()
    .WithUrl("http://localhost:53353/echo", options =>
    {
        options.Transports = HttpTransportType.WebSockets;
        options.HttpMessageHandlerFactory = _ =>
        {
            return factory.Server.CreateHandler();
        };
        options.WebSocketFactory = async (context, token) =>
        {
            var wsClient = factory.Server.CreateWebSocketClient();
            return await wsClient.ConnectAsync(context.Uri, default);
        };
    })
    .Build();

Cookie authentication sliding expiration can now be customized or suppressed using the new OnCheckSlidingExpiration. For example, this event can be used by a single-page app that needs to periodically ping the server without affecting the authentication session.

Delayed client certificate negotiation

Developers can now opt-in to using delayed client certificate negotiation by specifying ClientCertificateMode.DelayCertificate on the HttpsConnectionAdapterOptions. This will only work with HTTP/1.1 connections since HTTP/2 strictly forbids delayed certificate renegotiation. The caller of this API must buffer the request body before requesting the client certificate.

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel(options =>
{
    options.ConfigureHttpsDefaults(adapterOptions =>
    {
        adapterOptions.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
    });
});

var app = builder.Build();
app.Use(async (context, next) =>
{
    // Check if your desired criteria is met
    if (desiredState == true)
    {
        // Buffer the request body
        context.Request.EnableBuffering();
        var body = context.Request.Body;
        await body.DrainAsync(context.RequestAborted);
        body.Position = 0;

        // Request client certificate
        var cert = await context.Connection.GetClientCertificateAsync();

        //  Disable buffering on future requests if the client doesn't provide a cert
    } 
    return next(context);
});
app.MapGet("/", () => "Hello World!");
app.Run();

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!

27 comments

Leave a comment