From 81ad66c6cb28c718e5cc98d51596a7df277c3426 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 29 Jun 2026 14:03:12 -0500 Subject: [PATCH 1/2] [release-notes] ASP.NET Core in .NET 11 Preview 6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../11.0/preview/preview6/aspnetcore.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 release-notes/11.0/preview/preview6/aspnetcore.md diff --git a/release-notes/11.0/preview/preview6/aspnetcore.md b/release-notes/11.0/preview/preview6/aspnetcore.md new file mode 100644 index 0000000000..bff22d6f77 --- /dev/null +++ b/release-notes/11.0/preview/preview6/aspnetcore.md @@ -0,0 +1,164 @@ + +# ASP.NET Core in .NET 11 Preview 6 - Release Notes + +.NET 11 Preview 6 includes new ASP.NET Core features and improvements: + +- [Minimal API validation runs async validators](#minimal-api-validation-runs-async-validators) +- [Blazor Virtualize can scroll to an item](#blazor-virtualize-can-scroll-to-an-item) +- [OpenAPI documents default to 3.2 and describe union types](#openapi-documents-default-to-32-and-describe-union-types) +- [Short-circuit endpoints with an attribute](#short-circuit-endpoints-with-an-attribute) +- [SignalR refreshes authentication on long-lived connections](#signalr-refreshes-authentication-on-long-lived-connections) +- [Breaking changes](#breaking-changes) +- [Bug fixes](#bug-fixes) +- [Community contributors](#community-contributors) + +ASP.NET Core updates in .NET 11: + +- [What's new in ASP.NET Core in .NET 11](https://learn.microsoft.com/aspnet/core/release-notes/aspnetcore-11) + +## Minimal API validation runs async validators + +Minimal API validation now supports asynchronous validators end-to-end ([dotnet/aspnetcore #66487](https://github.com/dotnet/aspnetcore/pull/66487), [dotnet/aspnetcore #67183](https://github.com/dotnet/aspnetcore/pull/67183)). Preview 5 shipped the building blocks for asynchronous form validation in Blazor. Preview 6 adds the new asynchronous `DataAnnotations` APIs in the base libraries — `AsyncValidationAttribute`, `IAsyncValidatableObject`, and `Validator.ValidateObjectAsync` — and `Microsoft.Extensions.Validation` now runs them when an endpoint validates a request. + +This 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`: + +```csharp +using System.ComponentModel.DataAnnotations; +using System.Runtime.CompilerServices; + +public class ReservationRequest : IAsyncValidatableObject +{ + [Required] + public string Email { get; set; } = ""; + + public DateOnly Date { get; set; } + + 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 `GetValidationResultAsync`. Thank you [@Youssef1313](https://github.com/Youssef1313) for the implementation work on this feature. + +## Blazor Virtualize can scroll to an item + +The `Virtualize` component can now open at a specific item and scroll to any item on demand ([dotnet/aspnetcore #66753](https://github.com/dotnet/aspnetcore/pull/66753)). Two new public APIs make this possible: + +- `InitialIndex` positions the list at a given item on the first interactive render, so the list opens at that item without a flash of the first item. +- `ScrollToIndexAsync(int itemIndex, CancellationToken cancellationToken = default)` scrolls to an item at any time after the first render and returns a `Task` that completes when the target is aligned to the top of the viewport. + +```razor + +
@context.Name
+
+ + + +@code { + private Virtualize list = default!; + private IReadOnlyList products = ProductCatalog.All; + + private async Task GoToTop() => await list.ScrollToIndexAsync(0); +} +``` + +Out-of-range indexes are clamped to the valid range. If a second `ScrollToIndexAsync` call starts while one is still in flight, the last call wins. Calling `ScrollToIndexAsync` before the first interactive render throws `InvalidOperationException`; use `InitialIndex` to set the starting position instead. + +## OpenAPI documents default to 3.2 and describe union types + +Generated OpenAPI documents now target OpenAPI 3.2 by default ([dotnet/aspnetcore #67097](https://github.com/dotnet/aspnetcore/pull/67097), [dotnet/aspnetcore #67007](https://github.com/dotnet/aspnetcore/pull/67007)). Endpoints that return C# union types are now described with an `anyOf` schema listing each case type ([dotnet/aspnetcore #67001](https://github.com/dotnet/aspnetcore/pull/67001)). Unlike polymorphic types, union cases don't carry a `$type` discriminator, so a case schema such as `Kitten` reuses the standalone `#/components/schemas/Kitten` component instead of producing a duplicated, prefixed component. ApiExplorer detects a union through `JsonTypeInfoKind.Union` on the parent type, so the same metadata flows through to MVC controllers, minimal APIs, and external consumers such as Swashbuckle and NSwag. + +This builds on the Preview 5 support for declaring multiple response types per status code, so an endpoint that returns one of several shapes is now described accurately in the document. + +## 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 ([dotnet/aspnetcore #67249](https://github.com/dotnet/aspnetcore/pull/67249)). 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. An optional status code sets the response status when the endpoint is short-circuited. Thank you [@Porozhniakov](https://github.com/Porozhniakov) for contributing this feature! + +```csharp +[ShortCircuit(404)] +[Route("robots.txt")] +public class RobotsController : Controller +{ + [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. + +## SignalR refreshes authentication on long-lived connections + +SignalR connections can now refresh authentication without dropping the connection when the access token expires ([dotnet/aspnetcore #67400](https://github.com/dotnet/aspnetcore/pull/67400)). The server exposes a `/refresh` endpoint alongside `/negotiate` and reports the token lifetime in the negotiate response. The .NET client automatically re-authenticates before the token expires, so a hub connection that previously closed when its bearer token aged out can stay open. + +Enable the feature when mapping the hub: + +```csharp +app.MapHub("/chat", options => +{ + options.EnableAuthenticationRefresh = true; +}); +``` + +A hub can react to a refreshed identity by overriding `OnAuthenticationRefreshedAsync`, and the server can customize the refresh decision with `HttpConnectionDispatcherOptions.OnAuthenticationRefresh`: + +```csharp +public class ChatHub : Hub +{ + public override Task OnAuthenticationRefreshedAsync() + { + // The connection's User has been updated with the refreshed token. + return Task.CompletedTask; + } +} +``` + +## Breaking changes + +- **Custom minimal API validation resolvers** — `Microsoft.Extensions.Validation.IValidatableInfo` was split into `IValidatableTypeInfo`, `IValidatableParameterInfo`, and `IValidatablePropertyInfo`, and `ValidateContext.ValidationErrors` is now a read-only dictionary that you add to with the new `ValidateContext.AddValidationError` method ([dotnet/aspnetcore #67183](https://github.com/dotnet/aspnetcore/pull/67183)). Apps that use the built-in `[ValidatableType]` and `AddValidation()` are unaffected; only code that implements `IValidatableInfoResolver` directly needs to update. +- **Antiforgery and cross-origin CSRF share one verdict** — `CsrfProtectionMiddleware` no longer short-circuits a cross-origin request. It records its verdict on `IAntiforgeryValidationFeature`, and the request is rejected only when a component actually reads the form ([dotnet/aspnetcore #67082](https://github.com/dotnet/aspnetcore/pull/67082)). Apps that call `UseAntiforgery()` see no change. This unifies token antiforgery and cross-origin CSRF behind a single, lazily enforced verdict. + +## Bug fixes + +- **OpenAPI** + - Honored `DescriptionAttribute` on nullable value types during schema generation ([dotnet/aspnetcore #65245](https://github.com/dotnet/aspnetcore/pull/65245)). + - Fixed duplicate XML documentation IDs for generic properties and references ([dotnet/aspnetcore #64404](https://github.com/dotnet/aspnetcore/pull/64404)). + - Propagated `JsonSerializerOptions` correctly during OpenAPI generation ([dotnet/aspnetcore #66847](https://github.com/dotnet/aspnetcore/pull/66847)). +- **Caching** + - Added the key sub-delimiter between multi-value `Vary` header values in response and output caching ([dotnet/aspnetcore #66936](https://github.com/dotnet/aspnetcore/pull/66936)). +- **Metrics** + - Fixed the default value of `System.Diagnostics.Metrics.Meter.IsSupported` ([dotnet/aspnetcore #66846](https://github.com/dotnet/aspnetcore/pull/66846)). + +## Community contributors + +Thank you contributors! ❤️ + +- [@Alex-Sob](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3AAlex-Sob+milestone%3A11.0-preview6) +- [@Bellambharath](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3ABellambharath+milestone%3A11.0-preview6) +- [@classyk12](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Aclassyk12+milestone%3A11.0-preview6) +- [@desjoerd](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Adesjoerd+milestone%3A11.0-preview6) +- [@doominator42](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Adoominator42+milestone%3A11.0-preview6) +- [@GrantTotinov](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3AGrantTotinov+milestone%3A11.0-preview6) +- [@jesuszarate](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Ajesuszarate+milestone%3A11.0-preview6) +- [@medhatiwari](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Amedhatiwari+milestone%3A11.0-preview6) +- [@Porozhniakov](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3APorozhniakov+milestone%3A11.0-preview6) +- [@rdeveen](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Ardeveen+milestone%3A11.0-preview6) +- [@sheiksyedm](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3Asheiksyedm+milestone%3A11.0-preview6) +- [@TheAlexLichter](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+author%3ATheAlexLichter+milestone%3A11.0-preview6) From 3ad6005285147da5551ef667d41d3114f475a6e2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 29 Jun 2026 15:10:43 -0500 Subject: [PATCH 2/2] Correct ASP.NET Core P6 notes after testing against the Preview 6 SDK Verified each feature with runnable samples on the Preview 6 build: - Async validation: IAsyncValidatableObject also needs the sync Validate member, and AsyncValidationAttribute is overridden via IsValidAsync (not GetValidationResultAsync). - Virtualize: Items requires ICollection, so the sample uses List (IReadOnlyList does not compile). - [ShortCircuit]: robots.txt should use [ShortCircuit] (200), not [ShortCircuit(404)] which returns the body with a 404; added a minimal API example. OpenAPI 3.2 default + union anyOf schema and SignalR auth refresh verified accurate as written. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../11.0/preview/preview6/aspnetcore.md | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/release-notes/11.0/preview/preview6/aspnetcore.md b/release-notes/11.0/preview/preview6/aspnetcore.md index bff22d6f77..ee0e2f9af4 100644 --- a/release-notes/11.0/preview/preview6/aspnetcore.md +++ b/release-notes/11.0/preview/preview6/aspnetcore.md @@ -20,7 +20,7 @@ ASP.NET Core updates in .NET 11: Minimal API validation now supports asynchronous validators end-to-end ([dotnet/aspnetcore #66487](https://github.com/dotnet/aspnetcore/pull/66487), [dotnet/aspnetcore #67183](https://github.com/dotnet/aspnetcore/pull/67183)). Preview 5 shipped the building blocks for asynchronous form validation in Blazor. Preview 6 adds the new asynchronous `DataAnnotations` APIs in the base libraries — `AsyncValidationAttribute`, `IAsyncValidatableObject`, and `Validator.ValidateObjectAsync` — and `Microsoft.Extensions.Validation` now runs them when an endpoint validates a request. -This 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`: +This 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 (return no results when all the validation is asynchronous): ```csharp using System.ComponentModel.DataAnnotations; @@ -33,6 +33,9 @@ public class ReservationRequest : IAsyncValidatableObject public DateOnly Date { get; set; } + // IValidatableObject (synchronous) — no synchronous rules here. + public IEnumerable Validate(ValidationContext context) => []; + public async IAsyncEnumerable ValidateAsync( ValidationContext context, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -56,7 +59,7 @@ 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 `GetValidationResultAsync`. Thank you [@Youssef1313](https://github.com/Youssef1313) for the implementation work on this feature. +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. ## Blazor Virtualize can scroll to an item @@ -74,7 +77,7 @@ The `Virtualize` component can now open at a specific item and scroll to @code { private Virtualize list = default!; - private IReadOnlyList products = ProductCatalog.All; + private List products = ProductCatalog.All; private async Task GoToTop() => await list.ScrollToIndexAsync(0); } @@ -90,19 +93,24 @@ This builds on the Preview 5 support for declaring multiple response types per s ## 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 ([dotnet/aspnetcore #67249](https://github.com/dotnet/aspnetcore/pull/67249)). 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. An optional status code sets the response status when the endpoint is short-circuited. Thank you [@Porozhniakov](https://github.com/Porozhniakov) for contributing this feature! +The new `[ShortCircuit]` attribute marks an endpoint to run immediately after routing, skipping the rest of the middleware pipeline ([dotnet/aspnetcore #67249](https://github.com/dotnet/aspnetcore/pull/67249)). 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. Thank you [@Porozhniakov](https://github.com/Porozhniakov) for contributing this feature! ```csharp -[ShortCircuit(404)] +[ApiController] [Route("robots.txt")] -public class RobotsController : Controller +[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. +The same attribute works on minimal API endpoints, and the existing `ShortCircuit()` convention continues to work unchanged: + +```csharp +app.MapGet("/health", [ShortCircuit] () => "Healthy"); +``` ## SignalR refreshes authentication on long-lived connections