dotnet / AspNetCore.Docs

Documentation for ASP.NET Core
https://docs.microsoft.com/aspnet/core
Creative Commons Attribution 4.0 International
12.64k stars 25.29k forks source link

Document what's new in ASP.NET Core for .NET 9 Preview 7 #33031

Closed danroth27 closed 2 months ago

danroth27 commented 4 months ago

Description

Update the "What's new in ASP.NET Core 9.0" for .NET 9 Preview 7 .NET 9 preview releases

Page URL

https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0

Content source URL

https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/release-notes/aspnetcore-9.0.md

Document ID

4e75ad25-2c3f-b28e-6a91-ac79a9c683b6

Article author

@Rick-Anderson


Associated WorkItem - 291114

eerhardt commented 4 months ago

(Added to What's New as an include with PR #33318)

SignalR supports trimming and native AOT

Continuing the native AOT journey we started in .NET 8, we have enabled trimming and native AOT support for both SignalR client and server scenarios. You can now take advantage of the performance benefits of using native AOT in applications that use SignalR for real-time web communications.

Getting started

Using the dotnet new webapiaot template, and replacing Program.cs with the following SignalR code:

using Microsoft.AspNetCore.SignalR;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.AddSignalR();
builder.Services.Configure<JsonHubProtocolOptions>(o =>
{
    o.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapHub<ChatHub>("/chatHub");
app.MapGet("/", () => Results.Content("""
<!DOCTYPE html>
<html>
<head>
    <title>SignalR Chat</title>
</head>
<body>
    <input id="userInput" placeholder="Enter your name" />
    <input id="messageInput" placeholder="Type a message" />
    <button onclick="sendMessage()">Send</button>
    <ul id="messages"></ul>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chatHub")
            .build();

        connection.on("ReceiveMessage", (user, message) => {
            const li = document.createElement("li");
            li.textContent = `${user}: ${message}`;
            document.getElementById("messages").appendChild(li);
        });

        async function sendMessage() {
            const user = document.getElementById("userInput").value;
            const message = document.getElementById("messageInput").value;
            await connection.invoke("SendMessage", user, message);
        }

        connection.start().catch(err => console.error(err));
    </script>
</body>
</html>
""", "text/html"));

app.Run();

[JsonSerializable(typeof(string))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Produces a native Windows executable of 10 MB and a Linux executable of 10.9 MB.

Limitations

mkArtakMSFT commented 3 months ago

@halter73 the only customer-facing change worth mentioning is the new analyzer that you've done (AllowAnonymous / Authorize). Can you please drop a note as a comment here with the details about it? Thanks!

halter73 commented 3 months ago

The AllowAnonymous / Authorize changes were covered in preview 6. https://github.com/dotnet/AspNetCore.Docs/issues/32960#issuecomment-2214749440

halter73 commented 3 months ago

OpenIdConnectHandler adds support for Pushed Authorization Requests (PAR)

We'd like to thank @josephdecock from @DuendeSoftware for adding Pushed Authorization Requests (PAR) to ASP.NET Core's OpenIdConnectHandler. Joe described the background and motivation for enabling PAR in his API proposal as follows:

Pushed Authorization Requests (PAR) is a relatively new OAuth standard that improves the security of OAuth and OIDC flows by moving authorization parameters from the front channel to the back channel (that is, from redirect URLs in the browser to direct machine to machine http calls on the back end).

This prevents an attacker in the browser from

  • seeing authorization parameters (which could leak PII) and from
  • tampering with those parameters (e.g., the attacker could change the scope of access being requested).

Pushing the authorization parameters also keeps request URLs short. Authorize parameters might get very long when using more complex OAuth and OIDC features such as Rich Authorization Requests, and URLs that are long cause issues in many browsers and networking infrastructure.

The use of PAR is encouraged by the FAPI working group within the OpenID Foundation. For example, the FAPI2.0 Security Profile requires the use of PAR. This security profile is used by many of the groups working on open banking (primarily in Europe), in health care, and in other industries with high security requirements.

PAR is supported by a number of identity providers, including

  • Duende IdentityServer
  • Curity
  • Keycloak
  • Authlete

For preview7, we have decided to enable PAR by default if the identity provider's discovery document (usually found at .well-known/openid-configuration) advertises support for PAR, since it should provide enhanced security for providers that support it. If this causes problems, you can disable PAR via OpenIdConnectOptions.PushedAuthorizationBehavior as follows:

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(oidcOptions =>
    {
        // Other provider-specific configuration goes here.

        // The default value is PushedAuthorizationBehavior.UseIfAvailable.
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });

If you want to ensure that authentication only succeeds if PAR is used, you can use PushedAuthorizationBehavior.Require instead. This change also introduces a new OnPushAuthorization event to OpenIdConnectEvents which can be used customize the pushed authorization request or handle it manually. Please refer to the API proposal for more details.


@josephdecock @brockallen @JeremyLikness @blowdart @jennyf19 @janmarques @brentschmaltz Let me know if you think there's any improvements we should make to the above for the preview7 announcement.

captainsafia commented 3 months ago

Support calling ProducesProblem and ProducesValidationProblem on route groups

The ProducesProblem and ProducesValidationProblem extension methods have been updated to support application on route groups, as in the sample below. These methods can be used to indicate that all endpoints in a route group can return ProblemDetails or ValidationProblemDetails responses for the purposes of OpenAPI metadata.

var app = WebApplication.Create();

var todos = app.MapGroup("/todos")
    .ProducesProblem();

todos.MapGet("/", () => new Todo(1, "Create sample app", false));
todos.MapPost("/", (Todo todo) => Results.Ok(todo));

app.Run();

record Todo(int Id, string Title, boolean IsCompleted);

Problem and ValidationProblem result types support construction with IEnumerable<KeyValuePair<string, object?>> values

Prior to this preview, constructing Problem and ValidationProblem result types in minimal APIs required that the errors and extensions properties be initialized with an implementation of IDictionary<string, object?>. Starting in this release, these construction APIs support overloads that consume IEnumerable<KeyValuePair<string, object?>>.

using Microsoft.AspNetCore.Http;

var app = WebApplication.Create();

app.MapGet("/", () =>
{
    var extensions = new List<KeyValuePair<string, object>> { new("test", "value") };
    return TypedResults.Problem("This is an error with extensions", extensions: extensions);
});

Thanks to GitHub user "joegoldman2" for this contribution!

Microsoft.AspNetCore.OpenApi supports trimming and Native AOT

The new built-in OpenAPI support in ASP.NET Core now also supports trimming and Native AOT.

Get started

Create a new ASP.NET Core Web API (native AOT) project.

dotnet new webapiaot

Add the Microsoft.AspNetCore.OpenAPI package.

dotnet add package Microsoft.AspNetCore.OpenApi --prerelease

For this prerelease, you also need to add the latest Microsoft.OpenAPI package to avoid trimming warnings.

dotnet add package Microsoft.OpenApi

Update Program.cs to enable generating OpenAPI documents.

+ builder.Services.AddOpenApi();

var app = builder.Build();

+ app.MapOpenApi();

Publish the app.

dotnet publish

The app should publish cleanly using Native AOT without warnings.

Improvements to transformer registration APIs in Microsoft.AspNetCore.OpenApi

OpenAPI transformers support modifying the OpenAPI document, operations within the document, or schemas associated with types in the API. In this preview, the APIs for registering transformers on an OpenAPI document to provide a variety of options for registering transformers.

Previously, the following APIs where available for registering transformers:

OpenApiOptions UseTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions UseTransformer(IOpenApiDocumentTransformer transformer)
OpenApiOptions UseTransformer<IOpenApiDocumentTransformer>()
OpenApiOptions UseSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task>)
OpenApiOptions UseOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task>)

The new API set is as follows:

OpenApiOptions AddDocumentTransformer(Func<OpenApiDocument, OpenApiDocumentTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddDocumentTransformer(IOpenApiDocumentTransformer transformer)
OpenApiOptions AddDocumentTransformer<IOpenApiDocumentTransformer>()

OpenApiOptions AddSchemaTransformer(Func<OpenApiSchema, OpenApiSchemaTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddSchemaTransformer(IOpenApiSchemaTransformer transformer)
OpenApiOptions AddSchemaTransformer<IOpenApiSchemaTransformer>()

OpenApiOptions AddOperationTransformer(Func<OpenApiOperation, OpenApiOperationTransformerContext, CancellationToken, Task> transformer)
OpenApiOptions AddOperationTransformer(IOpenApiOperationTransformer transformer)
OpenApiOptions AddOperationTransformer<IOpenApiOperationTransformer>()

Thanks to GitHub user "martincostello" for contributing this API improvement to the package!

amcasey commented 3 months ago

Data Protection adds support for deleting keys

Historically, it has been intentionally impossible to delete data protection keys because doing so makes it impossible to decrypt any data protected with them (i.e. causing data loss). Fortunately, keys are quite small, so the impact of accumulating many of them is minor. However, in order to support very long running services, we've added the ability to explicitly delete (typically, very old) keys, accepting the risk of data loss in exchange for storage savings. Our guidance remains that data protection keys should not be deleted.

[I'd rather not include this code snippet, but here it is:]

var keyManager = services.GetService<IKeyManager>();
if (keyManager is IDeletableKeyManager deletableKeyManager)
{
    var utcNow = DateTimeOffset.UtcNow;
    var yearGo = utcNow.AddYears(-1);
    if (!deletableKeyManager.DeleteKeys(key => key.ExpirationDate < yearGo))
    {
        throw new InvalidOperationException("Failed to delete keys.");
    }
}
JamesNK commented 3 months ago

Customize Kestrel named pipe endpoints

Kestrel's named pipe support has been improved with advanced customization options. The new CreateNamedPipeServerStream method on the named pipe options allows pipes to be customized per-endpoint.

An example of where this is useful is a Kestrel app that requires two pipe endpoints but with different security. The CreateNamedPipeServerStream option can be used to create pipes with different security, depending on the pipe name.

var builder = WebApplication.CreateBuilder();

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenNamedPipe("pipe1");
    options.ListenNamedPipe("pipe2");
});

builder.WebHost.UseNamedPipes(options =>
{
    options.CreateNamedPipeServerStream = (context) =>
    {
        var pipeSecurity = CreatePipeSecurity(context.NamedPipeEndpoint.PipeName);

        return NamedPipeServerStreamAcl.Create(context.NamedPipeEndPoint.PipeName, PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte,
            context.PipeOptions, inBufferSize: 0, outBufferSize: 0, pipeSecurity);
    };
});
BrennanConroy commented 3 months ago

Add option to ExceptionHandlerMiddleware to choose status code based on exception

There is a new option when configuring the ExceptionHandlerMiddleware that allows application developers to choose what status code to return when an exception occurs during application request handling. This also changes the status code being set in the ProblemDetails response from the ExceptionHandlerMiddleware.

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    StatusCodeSelector = ex => ex is TimeoutException
        ? StatusCodes.Status503ServiceUnavailable
        : StatusCodes.Status500InternalServerError,
});

Thanks to @latonz for contributing this new option!

JamesNK commented 3 months ago

Opt-out of HTTP metrics on certain endpoints and requests

.NET 9 adds the ability to opt-out of HTTP metrics and not record a value for certain endpoints and requests. It's common for apps to have endpoints that are frequently called by automated systems, such as a health checks endpoint. Recording information about those requests isn't useful.

HTTP requests to an endpoint can be excluded from metrics by adding metadata. Either add the [DisableHttpMetrics] attribute to your Web API controller, SignalR hub or gRPC service; or call DisableHttpMetrics() when mapping endpoints in app startup:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz").DisableHttpMetrics();
app.Run();

In more advanced scenarios where a request doesn't map to an endpoint, or you want to opt-out HTTP requests dynamically, the MetricsDisabled property has been added to IHttpMetricsTagsFeature. Set MetricsDisabled to true during a HTTP request to opt-out.

// Middleware that conditionally opts-out HTTP requests.
app.Use(async (context, next) =>
{
    if (context.Request.Headers.ContainsKey("x-disable-metrics"))
    {
        context.Features.Get<IHttpMetricsTagsFeature>()?.MetricsDisabled = true;
    }

    await next(context);
});
JamesNK commented 3 months ago

Improved security and performance observability with Kestrel connection metrics

We've made a significant improvement to Kestrel's connection metrics by including metadata about why a connection failed. The kestrel.connection.duration metric now includes the connection close reason in the error.type attribute.

Here is a small sample of error.type values:

Previously, diagnosing Kestrel connection issues required a server to record detailed, low-level logging. The problem with logs is they're expensive to generate and store. And it can be difficult to find the right information amongst the noise.

Metrics are a much cheaper alternative that can be left on in a production environment with minimal impact. Collected metrics can drive dashboards and alerts. Once a problem is identified at a high-level with metrics, further investigation using logging and other tooling can begin.

We expect improved connection metrics to be useful in many scenarios:

Learn more about ASP.NET Core metrics here.

wadepickett commented 3 months ago

@Rick-Anderson and @tdykstra, It is not clear to me if it is OK now to do PR's adding the new sections (I have one ready to submit). Assuming the process would be to do the PR, get the review and merge to main, but ensure it is not merged to live. I don't remember us using a fork or anything last time.

wadepickett commented 3 months ago

Got my answer offline, will PR now, thanks!

Added PR #33318 SignalR supports trimming and native AOT