diff --git a/aspnetcore/release-notes/aspnetcore-11.md b/aspnetcore/release-notes/aspnetcore-11.md index 8a5d11882b51..62b24fb950fe 100644 --- a/aspnetcore/release-notes/aspnetcore-11.md +++ b/aspnetcore/release-notes/aspnetcore-11.md @@ -5,7 +5,7 @@ author: wadepickett description: Learn about the new features in ASP.NET Core in .NET 11. ms.author: wpickett ms.custom: mvc -ms.date: 06/09/2026 +ms.date: 07/10/2026 uid: aspnetcore-11 --- # What's new in ASP.NET Core in .NET 11 @@ -30,6 +30,10 @@ This section describes new features for Blazor Hybrid. This section describes new features for SignalR. +[!INCLUDE[](~/release-notes/aspnetcore-11/includes/signalr-authentication-refresh-preview-6.md)] + +[!INCLUDE[](~/release-notes/aspnetcore-11/includes/signalr-cancel-hub-invocations-preview-6.md)] + ## Minimal APIs This section describes new features for Minimal APIs. @@ -38,6 +42,10 @@ This section describes new features for Minimal APIs. [!INCLUDE[](~/release-notes/aspnetcore-11/includes/csharp-unions-preview-6.md)] +[!INCLUDE[](~/release-notes/aspnetcore-11/includes/async-validation-minimal-apis-preview-6.md)] + +[!INCLUDE[](~/release-notes/aspnetcore-11/includes/short-circuit-endpoints-attribute-preview-6.md)] + ## OpenAPI This section describes new features for OpenAPI. @@ -52,6 +60,8 @@ This section describes new features for OpenAPI. [!INCLUDE[](~/release-notes/aspnetcore-11/includes/openapi-schema-improvements-preview-5.md)] +[!INCLUDE[](~/release-notes/aspnetcore-11/includes/openapi-3-2-default-preview-6.md)] + ## Authentication and authorization This section describes new features for authentication and authorization. diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/async-validation-minimal-apis-preview-6.md b/aspnetcore/release-notes/aspnetcore-11/includes/async-validation-minimal-apis-preview-6.md new file mode 100644 index 000000000000..0f7b19a2bdb1 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-11/includes/async-validation-minimal-apis-preview-6.md @@ -0,0 +1,49 @@ +### Async validation for minimal APIs + +Minimal API validation supports asynchronous validators end-to-end. The base libraries add the asynchronous DataAnnotations APIs `AsyncValidationAttribute`, `IAsyncValidatableObject`, and `Validator.ValidateObjectAsync`, and `Microsoft.Extensions.Validation` runs them when an endpoint validates a request. + + + +Asynchronous validation lets a validation rule do real work, such as a database lookup or a remote API call, without blocking a thread. A model implements `IAsyncValidatableObject` and returns validation results as an `IAsyncEnumerable`. Because `IAsyncValidatableObject` extends `IValidatableObject`, also implement the synchronous `Validate` method. When a type validates asynchronously only, throw from `Validate` so it isn't silently validated through the synchronous APIs: + +```csharp +using System.ComponentModel.DataAnnotations; +using System.Runtime.CompilerServices; + +public class ReservationRequest : IAsyncValidatableObject +{ + [Required] + public string Email { get; set; } = ""; + + public DateOnly Date { get; set; } + + // IValidatableObject (synchronous) — this type validates asynchronously only. + public IEnumerable Validate(ValidationContext context) => + throw new NotSupportedException("Validate this type with ValidateAsync."); + + public async IAsyncEnumerable ValidateAsync( + ValidationContext context, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var rooms = context.GetRequiredService(); + if (!await rooms.HasAvailabilityAsync(Date, cancellationToken)) + { + yield return new ValidationResult( + "No rooms are available on that date.", [nameof(Date)]); + } + } +} +``` + +Register validation and the framework validates the request before the endpoint runs: + +```csharp +builder.Services.AddValidation(); + +app.MapPost("/reservations", (ReservationRequest request) => + Results.Ok(request)); +``` + +Validators run concurrently where possible: asynchronous attributes on the same member start together, collection items validate in parallel, and the framework preserves the existing ordering between member, type, and `IValidatableObject` validation. For attribute-based rules, derive from `AsyncValidationAttribute` and override its `IsValidAsync` method. + +Thank you [@Youssef1313](https://github.com/Youssef1313) for the implementation work on this feature! diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/csharp-unions-preview-6.md b/aspnetcore/release-notes/aspnetcore-11/includes/csharp-unions-preview-6.md index 177e6a4fce50..ec84db4b8751 100644 --- a/aspnetcore/release-notes/aspnetcore-11/includes/csharp-unions-preview-6.md +++ b/aspnetcore/release-notes/aspnetcore-11/includes/csharp-unions-preview-6.md @@ -1,11 +1,22 @@ ### C# union types -ASP.NET Core supports [C# union types](/dotnet/csharp/whats-new/csharp-14#union-types) (new in .NET 11) anywhere [`System.Text.Json`](/dotnet/standard/serialization/system-text-json/overview) is used: JSON request and response bodies in Minimal APIs and MVC, SignalR's `JsonHubProtocol`, Blazor JavaScript interop, persistent component state, and prerendered component parameters. +C# union types are a preview language feature in .NET 11 (see the [C# release notes](/dotnet/csharp/whats-new/csharp-14#union-types)), and [`System.Text.Json`](/dotnet/standard/serialization/system-text-json/overview) serializes them natively. Because ASP.NET Core uses `System.Text.Json` for JSON, union types work as JSON request bodies and return types throughout the stack with no ASP.NET-specific configuration: + +* Minimal APIs — union body parameters and return types, including `Task`, `IAsyncEnumerable`, and `Results`, in both the runtime and source-generated request delegates. +* MVC and Razor Pages — union types as `[FromBody]` parameters and action or page-handler return types. +* SignalR — union types as hub method parameters, return values, and stream items when using the JSON hub protocol. +* Blazor — union types as component parameters, JavaScript interop arguments and results, and persisted component state. ```csharp -public union UnionIntString(int, string); +// Requires preview in the project file. +public record class Dog(string Name); +public record class Cat(int Lives); +public union Pet(Dog, Cat); -app.MapGet("/value", () => new UnionIntString(42)); +// The active case is serialized on the way out and described with anyOf in OpenAPI. +app.MapGet("/pets/{id}", Pet (int id) => id == 0 ? new Dog("Rex") : new Cat(9)); ``` -Union types aren't supported for non-body binding sources such as route values, query strings, headers, and form fields. \ No newline at end of file +For OpenAPI, an endpoint that returns a union is described with an `anyOf` schema listing each case type. Unlike polymorphic types, union cases don't carry a `$type` discriminator, so a case such as `Dog` reuses the standalone `#/components/schemas/Dog` component instead of a duplicated, prefixed one. ApiExplorer detects a union through `JsonTypeInfoKind.Union`, so the schema also flows through to Swashbuckle and NSwag. + +A few limits apply. Only JSON request bodies and responses are supported — binding a union from the query string, route values, headers, or form fields isn't yet available. When multiple cases serialize to the same JSON shape, disambiguate them with a `[JsonUnion]` classifier. SignalR unions require the JSON hub protocol; the MessagePack and Newtonsoft.Json protocols don't support unions. \ No newline at end of file diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/openapi-3-2-default-preview-6.md b/aspnetcore/release-notes/aspnetcore-11/includes/openapi-3-2-default-preview-6.md new file mode 100644 index 000000000000..c1f05e630758 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-11/includes/openapi-3-2-default-preview-6.md @@ -0,0 +1,12 @@ +### OpenAPI 3.2 by default + +Generated OpenAPI documents now target OpenAPI 3.2 by default. Documents continue to generate as before; set the document version explicitly if you need to target an earlier version for tooling that hasn't adopted 3.2 yet. + +To target an earlier version, specify it when calling : + +```csharp +builder.Services.AddOpenApi(options => +{ + options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1; +}); +``` diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/short-circuit-endpoints-attribute-preview-6.md b/aspnetcore/release-notes/aspnetcore-11/includes/short-circuit-endpoints-attribute-preview-6.md new file mode 100644 index 000000000000..23467646d939 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-11/includes/short-circuit-endpoints-attribute-preview-6.md @@ -0,0 +1,26 @@ +### Short-circuit endpoints with an attribute + +The new `[ShortCircuit]` attribute marks an endpoint to run immediately after routing, skipping the rest of the middleware pipeline. This is the attribute form of the existing `ShortCircuit()` endpoint convention, so it can be applied directly to MVC controllers and actions. + + + +Short-circuiting is useful for endpoints that don't need authentication, CORS, or other middleware — for example a health check or a robots.txt response — and it avoids the cost of running that middleware. The endpoint still runs and produces its response. Pass an optional status code, such as `[ShortCircuit(404)]`, to set the response status code. + +```csharp +[ApiController] +[Route("robots.txt")] +[ShortCircuit] +public class RobotsController : ControllerBase +{ + [HttpGet] + public IActionResult Get() => Content("User-agent: *\nDisallow:", "text/plain"); +} +``` + +The same attribute works on minimal API endpoints, and the existing `ShortCircuit()` convention continues to work unchanged: + +```csharp +app.MapGet("/health", [ShortCircuit] () => "Healthy"); +``` + +Thank you [@Porozhniakov](https://github.com/Porozhniakov) for contributing this feature! diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/signalr-authentication-refresh-preview-6.md b/aspnetcore/release-notes/aspnetcore-11/includes/signalr-authentication-refresh-preview-6.md new file mode 100644 index 000000000000..fea11296bcf8 --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-11/includes/signalr-authentication-refresh-preview-6.md @@ -0,0 +1,45 @@ +### SignalR authentication refresh + +SignalR connections can refresh authentication without dropping the connection when the access token expires. The server exposes a `/refresh` endpoint alongside `/negotiate` and reports the token lifetime in the negotiate response. The .NET client re-authenticates before the token expires, so a hub connection that previously closed when its bearer token aged out can stay open. This feature is implemented for the .NET client; the JavaScript/TypeScript client and Azure SignalR Service support are in progress. + + + +Enable the feature per hub on the server: + +```csharp +app.MapHub("/chat", options => +{ + options.EnableAuthenticationRefresh = true; + + // Optional: decide whether a given connection may refresh. + options.OnAuthenticationRefresh = context => ValueTask.FromResult(true); +}); +``` + +A hub can react to a refreshed identity by overriding `OnAuthenticationRefreshedAsync`: + +```csharp +public class ChatHub : Hub +{ + public override Task OnAuthenticationRefreshedAsync() + { + // The connection's User has been updated with the refreshed token. + return Task.CompletedTask; + } +} +``` + +Automatic refresh is on by default in the .NET client and is configurable with `WithAuthenticationRefresh`: + +```csharp +var connection = new HubConnectionBuilder() + .WithUrl("https://example.com/chat") + .WithAuthenticationRefresh(options => + { + // EnableAutoRefresh is true by default. + options.RefreshBeforeExpiration = TimeSpan.FromMinutes(1); + options.OnAuthenticationRefreshed = context => Task.CompletedTask; + options.OnAuthenticationRefreshFailed = context => Task.CompletedTask; + }) + .Build(); +``` diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/signalr-cancel-hub-invocations-preview-6.md b/aspnetcore/release-notes/aspnetcore-11/includes/signalr-cancel-hub-invocations-preview-6.md new file mode 100644 index 000000000000..06b7d5e7cc3c --- /dev/null +++ b/aspnetcore/release-notes/aspnetcore-11/includes/signalr-cancel-hub-invocations-preview-6.md @@ -0,0 +1,22 @@ +### Cancel hub invocations from the client + +The SignalR client can cancel a regular, non-streaming hub method invocation. Previously only streaming invocations could be canceled from the client. Now, when you pass a to and cancel it, the client sends a cancellation message and the hub method's `CancellationToken` parameter is triggered on the server. + +```csharp +// Client — canceling the token cancels the server-side invocation. +using var cts = new CancellationTokenSource(); +var work = connection.InvokeAsync("LongRunningWork", cts.Token); +// ... +cts.Cancel(); +``` + +```csharp +// Hub — accept a CancellationToken to observe client cancellation. +public class WorkHub : Hub +{ + public async Task LongRunningWork(CancellationToken cancellationToken) + { + await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken); + } +} +```