Updates to ASP.NET Core in .NET 7 RC1

Updates to ASP.NET Core in .NET 7 RC1

.NET 7 Release Candidate 1 (RC1) is now available, including many significant new improvements to ASP.NET Core.

Last updated 9/15/2022 9:23 AM
Daniel Roth
18 min read
Category
.NET
Tags
.NET C# ASP.NET Core

Original link: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-rc-1/

Original author: Daniel Roth

Translation: Desert Wolf at the End (with Google Translate)

.NET 7 Release Candidate 1 (RC1) is now available, including many significant new improvements to ASP.NET Core.

Here is a summary of the new features in this preview:

  • Dynamic authentication requests in Blazor WebAssembly
  • Handling location change events
  • Blazor WebAssembly debugging improvements
  • .NET WebAssembly build tools for .NET 6 projects
  • .NET JavaScript interop on WebAssembly
  • Kestrel full certificate chain improvements
  • Faster HTTP/2 uploads
  • HTTP/3 improvements
  • Experimental Kestrel support for WebTransport over HTTP/3
  • Experimental OpenAPI support for gRPC JSON transcoding
  • Rate limiting middleware improvements
  • macOS development certificate improvements

For more details on the ASP.NET Core work planned for .NET 7, see the full ASP.NET Core roadmap for .NET 7 on GitHub.

Getting started

To get started with ASP.NET Core in .NET 7 Release Candidate 1, install the .NET 7 SDK.

If you are using Visual Studio on Windows, we recommend installing the latest Visual Studio 2022 preview. If you are on macOS, we recommend installing the latest Visual Studio 2022 for Mac preview.

To install the latest .NET WebAssembly build tools, run the following command from an elevated command prompt:

dotnet workload install wasm-tools

Upgrading existing projects

To upgrade an existing ASP.NET Core app from .NET 7 Preview 7 to .NET 7 RC1:

  • Update all Microsoft.AspNetCore.* package references to .7.0.0-rc.1.*
  • Update all Microsoft.Extensions.* package references to .7.0.0-rc.1.*

Also see the full list of breaking changes in ASP.NET Core for .NET 7.

Dynamic authentication requests in Blazor WebAssembly

Blazor provides out-of-the-box support for authentication using OpenID Connect and various identity providers, including Azure Active Directory (Azure AD) and Azure AD B2C. In .NET 7, Blazor now supports creating dynamic authentication requests at runtime with custom parameters to handle more advanced authentication scenarios in Blazor WebAssembly apps. To specify additional parameters, use the new InteractiveRequestOptions type and the NavigateToLogin helper method on NavigationManager.

For example, you can specify a login hint for the identity provider for authentication like this:

InteractiveRequestOptions requestOptions = new()
{
    Interaction = InteractionType.SignIn,
    ReturnUrl = NavigationManager.Uri,
};
requestOptions.TryAddAdditionalParameter("login_hint", "user@example.com");
NavigationManager.NavigateToLogin("authentication/login", requestOptions);

Similarly, you can specify the OpenID Connect prompt parameter, for example, when you want to force an interactive login:

InteractiveRequestOptions requestOptions = new()
{
    Interaction = InteractionType.SignIn,
    ReturnUrl = NavigationManager.Uri,
};
requestOptions.TryAddAdditionalParameter("prompt", "login");
NavigationManager.NavigateToLogin("authentication/login", requestOptions);

You can also specify these options when using IAccessTokenProvider directly to request a token:

var accessTokenResult = await AccessTokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "SecondAPI" }
    });

if (!accessTokenResult.TryGetToken(out var token))
{
    accessTokenResult.InteractiveOptions.AddAdditionalParameter("login_hint", "user@example.com");
    NavigationManager.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, accessTokenResult.InteractionOptions);
}

When a token cannot be obtained, you can also specify authentication request options via AuthorizationMessageHandler:

try
{
    await httpclient.Get("/orders");

}
catch (AccessTokenNotAvailableException ex)
{
    ex.Redirect(requestOptions =>
    {
        requestOptions.AddAdditionalParameter("login_hint", "user@example.com");
    });
}

Any additional parameters specified for authentication requests will be passed to the underlying authentication library, which then handles them.

Note: Specifying additional parameters for msal.js is not fully implemented yet but is expected to be completed in an upcoming release.

Handling location change events

Blazor in .NET 7 now supports handling location change events. This allows you to warn users about unsaved work or perform related actions when they navigate between pages.

Use the RegisterLocationChangingHandler method on the NavigationManager service to register a handler for location change events. Your handler can then perform asynchronous work during navigation, or cancel the navigation by calling PreventNavigation on the LocationChangingContext. RegisterLocationChangingHandler returns an IDisposable instance that removes the corresponding location change handler when disposed.

For example, the following handler prevents navigation to the counter page:

var registration = NavigationManager.RegisterLocationChangingHandler(async context =>
{
    if (context.TargetLocation.EndsWith("counter"))
    {
        context.PreventNavigation();
    }
});

Note that your handler will only be called for internal navigations within the app. External navigations can only be handled synchronously using the beforeunload event in JavaScript.

The new NavigationLock component makes it easier to handle common scenarios for location change events. NavigationLock exposes an OnBeforeInternalNavigation callback that you can use to intercept and handle internal location change events. If you also want users to confirm external navigations, you can use the ConfirmExternalNavigations property, which will intercept the beforeunload event and trigger a browser-specific prompt.

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    ...
</EditForm>
<NavigationLock OnBeforeInternalNavigation="ConfirmNavigation" ConfirmExternalNavigation />

@code {
    private readonly EditContext editContext;

    ...

    // Called only for internal navigations
    // External navigations will trigger a browser specific prompt
    async Task ConfirmNavigation(LocationChangingContext context)
    {
        if (editContext.IsModified())
        {
            var isConfirmed = await JS.InvokeAsync<bool>("window.confirm", "Are you sure you want to leave this page?");

            if (!isConfirmed)
            {
                context.PreventNavigation();
            }
        }
    }
}

Blazor WebAssembly debugging improvements

Blazor WebAssembly debugging in .NET 7 now includes the following improvements:

  • Support for Just My Code settings to show or hide type members not in user code
  • Support for inspecting multi-dimensional arrays
  • The call stack now displays the correct names for async methods
  • Improved expression evaluation
  • Correct handling of the new keyword for derived members
  • Support for debugger-related attributes in System.Diagnostics

.NET WebAssembly build tools for .NET 6 projects

You can now use the .NET WebAssembly build tools for .NET 6 projects when using the .NET 7 SDK. The new wasm-tools-net6 workload includes the .NET WebAssembly build tools for .NET 6 projects so that they can be used with the .NET 7 SDK. To install the new wasm-tools-net6 workload, run the following command from an elevated command prompt:

dotnet workload install wasm-tools-net6

The existing wasm-tools workload installs the .NET WebAssembly build tools for .NET 7 projects. However, the .NET 7 version of the .NET WebAssembly build tools is not compatible with existing projects built using .NET 6. Projects that need to support both .NET 6 and .NET 7 using the .NET WebAssembly build tools will need to use multi-targeting.

.NET JavaScript interop on WebAssembly

.NET 7 introduces a new low-level mechanism for using .NET in JavaScript-based applications. With this new JavaScript interop feature, you can call .NET code from JavaScript using the .NET WebAssembly runtime, and you can call JavaScript functionality from .NET without relying on the Blazor UI component model.

The easiest way to see the new JavaScript interop feature in action is to use the new experimental templates in the wasm-experimental workload:

dotnet workload install wasm-experimental

This workload contains two project templates: WebAssembly Browser App and WebAssembly Console App. These templates are experimental, meaning their developer workflows are not fully polished yet (for example, these templates do not run in Visual Studio yet). However, the .NET and JavaScript APIs used in these templates are supported in .NET 7 and provide a foundation for using .NET on WebAssembly via JavaScript.

You can create a WebAssembly browser application by running:

dotnet new wasmbrowser

This template creates a simple web application that demonstrates using both .NET and JavaScript in the browser. The WebAssembly Console App is similar but runs as a Node.js console application instead of a browser-based web application.

The JavaScript module in main.js in the created sample project demonstrates how to run .NET code from JavaScript. The relevant APIs are imported from dotnet.js. These APIs allow you to set up named modules that can be imported into C# code, and to call methods exposed by .NET code, including Program.Main:

import { dotnet } from "./dotnet.js";

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);

// Setup the .NET WebAssembly runtime
const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } =
  await dotnet
    .withDiagnosticTracing(false)
    .withApplicationArgumentsFromQuery()
    .create();

// Set module imports that can be called from .NET
setModuleImports("main.js", {
  window: {
    location: {
      href: () => globalThis.window.location.href,
    },
  },
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting(); // Call into .NET from JavaScript
console.log(text);

document.getElementById("out").innerHTML = `${text}`;
await runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); // Run Program.Main

To import a JavaScript function so that it can be called from C#, use the new JSImportAttribute on a matching method signature:

[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();

The first parameter of JSImportAttribute is the name of the JavaScript function to import, and the second parameter is the module name, both set by the setModuleImports call in main.js.

In the imported method signature, you can use .NET types for parameters and return values, which will be marshaled for you. Use JSMarshalAsAttribute<T> to control how imported method parameters are marshaled. For example, you can choose to marshal a long as JSType.Number or JSType.BigInt. You can pass Action/Func callbacks as parameters, which will be marshaled as callable JavaScript functions. You can pass both JavaScript and managed object references, which will be marshaled as proxy objects, keeping the objects alive across the boundary until the proxy is garbage collected. You can also import and export async methods with Task return values, which will be marshaled as JavaScript promises. For most encapsulated types, they work bidirectionally as parameters and return values on imported and exported methods.

Use JSExportAttribute to export a .NET method so that it can be called from JavaScript:

[JSExport]
internal static string Greeting()
{
    var text = $"Hello, World! Greetings from {GetHRef()}";
    Console.WriteLine(text);
    return text;
}

Blazor provides its own JavaScript interop mechanism based on the IJSRuntime interface, which is uniformly supported across all Blazor hosting models. This common asynchronous abstraction enables library authors to build JavaScript interop libraries that can be shared across the Blazor ecosystem, and it remains the recommended way to perform JavaScript interop in Blazor. However, in Blazor WebAssembly applications, you also have the option of using IJSInProcessRuntime for synchronous JavaScript interop calls, or even IJSUnmarshalledRuntime for unmarshalled calls. IJSUnmarshalledRuntime was tricky to use and only partially supported. In .NET 7, IJSUnmarshalledRuntime is now obsolete and should be replaced by the [JSImport]/[JSExport] mechanism. Blazor does not directly expose the dotnet runtime instance used from JavaScript, but it can still be accessed via .getDotnetRuntime(0). You can also import JavaScript modules by calling JSHost.ImportAsync from C# code, which can make module exports visible to [JSImport].

Kestrel full certificate chain improvements

The HttpsConnectionAdapterOptions type has a new property ServerCertificateChain of type X509Certificate2Collection, which makes it easier to validate certificate chains by allowing you to specify a full chain including intermediate certificates. For more information, see dotnet/aspnetcore#21513.

Faster HTTP/2 uploads

We have increased the default HTTP/2 upload connection window size for Kestrel from 128 KB to 1 MB, which significantly improves HTTP/2 upload speeds over high-latency connections using Kestrel's default configuration.

After introducing only 10 ms of artificial latency, we tested the impact of this increase by uploading a 108 MB file using a single stream on localhost and saw an upload speed improvement of approximately 6x.

The following screenshot compares the time to upload 108 MB in Edge's DevTools network tab:

  • Before: 26.9 seconds
  • After: 4.3 seconds

HTTP/3 improvements

.NET 7 RC1 continues to improve Kestrel's support for HTTP/3. The two main areas of improvement are feature parity with HTTP/1.1 and HTTP/2, and performance.

The biggest feature in this release is full support for ListenOptions.UseHttps with HTTP/3. Kestrel provides advanced options for configuring connection certificates, such as intercepting Server Name Indication (SNI).

The following example shows how to use an SNI callback to resolve TLS options:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(8080, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        listenOptions.UseHttps(new TlsHandshakeCallbackOptions
        {
            OnConnection = context =>
            {
                var options = new SslServerAuthenticationOptions
                {
                    ServerCertificate = ResolveCertForHost(context.ClientHelloInfo.ServerName)
                };
                return new ValueTask<SslServerAuthenticationOptions>(options);
            },
        });
    });
});

We have also done significant work to reduce allocations in HTTP/3 in .NET 7 RC1. You can see some of these improvements here:

Experimental Kestrel support for WebTransport over HTTP/3

We are excited to announce built-in experimental support for WebTransport based on HTTP/3 in Kestrel. This feature was written by our excellent intern Daniel! WebTransport is a new draft specification for a transport protocol similar to WebSockets, allowing multiple streams per connection. This is useful for splitting communication channels and thus avoiding head-of-line blocking. For example, consider a web-based online game where game state is transmitted over one bidirectional stream, player voice for voice chat over another bidirectional stream, and player controls over a unidirectional stream. With WebSockets, all of this would need to be placed on separate connections or compressed into a single stream. With WebTransport, you can keep all traffic on one connection but split it into its own streams, so if one stream blocks, the other streams continue uninterrupted.

Further details will be provided in a separate blog post.

Experimental OpenAPI support for gRPC JSON transcoding

gRPC JSON transcoding is a new feature in .NET 7 for converting gRPC APIs into RESTful APIs.

.NET 7 RC1 adds experimental support for generating OpenAPI from gRPC transcoded RESTful APIs. OpenAPI with gRPC JSON transcoding is a highly requested feature, and we are excited to offer a way to combine these great technologies. The NuGet package is experimental in .NET 7, giving us time to explore the best way to integrate these features.

To enable OpenAPI with gRPC JSON transcoding:

  • Add a package reference to Microsoft.AspNetCore.Grpc.Swagger. The version must be 0.3.0-xxx or later.
  • Configure Swashbuckle at startup. The AddGrpcSwagger method configures Swashbuckle to include gRPC endpoints.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc().AddJsonTranscoding();
builder.Services.AddGrpcSwagger();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1",
        new OpenApiInfo { Title = "gRPC transcoding", Version = "v1" });
});

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.MapGrpcService<GreeterService>();

app.Run();

To confirm that Swashbuckle is generating Swagger for RESTful gRPC services, start the application and navigate to the Swagger UI page:

Rate limiting middleware improvements

We have added several features to the rate limiting middleware in .NET 7 RC1 to make it more powerful and easier to use.

We added attributes that can be used to enable or disable rate limiting on a given endpoint. For example, the following shows how to apply the named policy MyControllerPolicy to a controller:

public class MyController : Controller
{
    [EnableRateLimitingAttribute("MyControllerPolicy")]
    public IActionResult Index()
    {
        return View();
    }
}

You can also completely disable rate limiting on a given endpoint or set of endpoints. Suppose you enable rate limiting on a set of endpoints:

app.MapGroup("/public/todos").RequireRateLimiting("MyGroupPolicy");

Then you can disable rate limiting for a specific endpoint in that group as follows:

app.MapGroup("/public/todos/donothing").DisableRateLimiting();

You can now also apply policies directly to endpoints. Unlike named policies, policies added in this way do not need to be registered in RateLimiterOptions. Suppose you have defined a policy type:

public class MyRateLimiterPolicy : IRateLimiterPolicy<string>
{
...
}

You can add an instance of it directly to an endpoint like this:

app.MapGet("/", () => "Hello World!").RequireRateLimiting(new MyRateLimiterPolicy());

Finally, we updated the RateLimiterOptions convenience methods to accept Action<Options> instead of Options instances, and also added extension methods on IServiceCollection for using rate limiting. So, to enable all the above rate limiting policies in your app, you can do:

builder.Services.AddRateLimiter(options =>
{
    options.AddTokenBucketLimiter("MyControllerPolicy", options =>
    {
        options.TokenLimit = 1;
        options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        options.QueueLimit = 1;
        options.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
        options.TokensPerPeriod = 1;
    })
    .AddPolicy<string>("MyGroupPolicy", new MyRateLimiterPolicy());
});

This applies the TokenBucketLimiter to your controller, your custom MyRateLimiterPolicy to endpoints matching ./public/todos (except /public/todos/donothing), and your custom MyRateLimiterPolicy to /.

macOS development certificate improvements

In this release, we have made several major quality improvements to the experience of using HTTPS development certificates on macOS, significantly reducing the number of authentication prompts that appear when creating, trusting, reading, and deleting ASP.NET Core HTTPS development certificates. This has been a pain point for ASP.NET Core developers on macOS when using development certificates in their workflows.

In this release, development certificates generated on macOS via the dotnet dev-certs tool have a narrower trust scope, will now be added to per-user trust settings instead of system-wide, and Kestrel will be able to bind to these new certificates without needing access to the system keychain. Some quality improvements have also been made as part of this work, such as rewording some user-facing messages for clarity and accuracy.

Together, these changes result in a smoother experience and fewer password prompts when using HTTPS during development on macOS.

See the new Blazor updates in action!

For live demonstrations of dynamic authentication requests in Blazor WebAssembly, Blazor WebAssembly debugging improvements, and .NET JavaScript interop on WebAssembly, check out our recent Blazor Community Standup:

Give feedback

We hope you enjoy the ASP.NET Core previews in .NET 7. Let us know what you think about these new improvements by submitting issues on GitHub.

Thank you for trying ASP.NET Core!

Keep Exploring

Related Reading

More Articles
Same category / Same tag 6/20/2024

CodeWF.EventBus: Lightweight Event Bus for Smoother Communication

CodeWF.EventBus is a flexible event bus library that enables decoupled communication between modules. It supports various .NET project types such as WPF, WinForms, ASP.NET Core, etc. With a clean design, it easily implements command publishing and subscribing, as well as requests and responses. Through orderly event handling, it ensures events are properly processed. Simplify your code and improve system maintainability.

Continue Reading
Same category / Same tag 1/19/2024

FluentValidation Validation Tutorial Based on .NET

FluentValidation is a validation framework based on .NET development. It is open-source, free, and elegant, supporting chained operations, easy to understand, feature-complete, and can be deeply integrated with MVC5, WebApi2, and ASP.NET Core. The component provides over a dozen commonly used validators, good scalability, support for custom validators, and support for localization and multilingual.

Continue Reading