|
2 | 2 |
|
3 | 3 | Here's a summary of what's new in ASP.NET Core in this preview release:
|
4 | 4 |
|
5 |
| -- [Feature](#feature) |
| 5 | +- [Configure suppressing exception handler diagnostics](#configure-suppressing-exception-handler-diagnostics) |
| 6 | +- [Avoid cookie login redirects for known API endpoints](#avoid-cookie-login-redirects-for-known-api-endpoints) |
| 7 | +- [Passkey authentication improvements](#passkey-authentication-improvements) |
| 8 | +- [Support for the .localhost top-level domain](#support-for-the-localhost-top-level-domain) |
| 9 | +- [Use PipeReader support in System.Text.Json](#use-pipereader-support-in-systemtextjson) |
| 10 | +- [Enhanced validation for classes and records](#enhanced-validation-for-classes-and-records) |
| 11 | +- [Blazor improvements](#blazor-improvements) |
| 12 | +- [OpenAPI.NET dependency upgraded to stable release](#openapinet-dependency-upgraded-to-stable-release) |
6 | 13 |
|
7 | 14 | ASP.NET Core updates in .NET 10:
|
8 | 15 |
|
9 | 16 | - [What's new in ASP.NET Core in .NET 10](https://learn.microsoft.com/aspnet/core/release-notes/aspnetcore-10.0) documentation.
|
10 | 17 | - [Breaking changes](https://docs.microsoft.com/dotnet/core/compatibility/10.0#aspnet-core)
|
11 | 18 | - [Roadmap](https://github.com/dotnet/aspnetcore/issues/59443)
|
12 | 19 |
|
13 |
| -## Feature |
| 20 | +## Configure suppressing exception handler diagnostics |
14 | 21 |
|
15 |
| -Something about the feature |
| 22 | +A new configuration option has been added to the [ASP.NET Core exception handler middleware](https://learn.microsoft.com/aspnet/core/fundamentals/error-handling#exception-handler-page) to control diagnostic output: `ExceptionHandlerOptions.SuppressDiagnosticsCallback`. This callback is passed context about the request and exception, allowing you to add logic that determines whether the middleware should write exception logs and other telemetry. |
| 23 | + |
| 24 | +This setting is useful when you know an exception is transient, or has been handled by the exception handler middleware, and don't want the error logs written to your observability platform. |
| 25 | + |
| 26 | +Additionally, the middleware's default behavior has changed: it no longer writes exception diagnostics for exceptions handled by `IExceptionHandler`. Based on user feedback, logging handled exceptions at the error level was often undesirable when `IExceptionHandler.TryHandleAsync` returned `true`. |
| 27 | + |
| 28 | +You can revert to the previous behavior by configuring `SuppressDiagnosticsCallback`: |
| 29 | + |
| 30 | +```csharp |
| 31 | +app.UseExceptionHandler(new ExceptionHandlerOptions |
| 32 | +{ |
| 33 | + SuppressDiagnosticsCallback = context => false; |
| 34 | +}); |
| 35 | +``` |
| 36 | + |
| 37 | +For more information about this breaking change, see https://github.com/aspnet/Announcements/issues/524. |
| 38 | + |
| 39 | +## Avoid cookie login redirects for known API endpoints |
| 40 | + |
| 41 | +By default, unauthenticated and unauthorized requests made to known API endpoints protected by cookie authentication now result in 401 and 403 responses rather than redirecting to a login or access denied URI. Redirecting unauthenticated requests to a login page doesn't usually make sense for API endpoints which typically rely on 401 and 403 status codes rather than HTML redirects to communicate authentication and authorization failures. |
| 42 | + |
| 43 | +API [endpoints](https://learn.microsoft.com/aspnet/core/fundamentals/routing) are identified using the new `IApiEndpointMetadata` interface, and metadata implementing the new interface has been added automatically to the following: |
| 44 | + |
| 45 | +- `[ApiController]` endpoints |
| 46 | +- Minimal API endpoints that read JSON request bodies or write JSON responses |
| 47 | +- Endpoints using `TypedResults` return types |
| 48 | +- SignalR endpoints |
| 49 | + |
| 50 | +When `IApiEndpointMetadata` is present, the cookie authentication handler now returns appropriate HTTP status codes (401 for unauthenticated requests, 403 for forbidden requests) instead of redirecting. |
| 51 | + |
| 52 | +If you want to prevent this new behavior and always redirect to the login and access denied URIs for unauthenticated or unauthorized requests regardless of the target endpoint, you can override the `RedirectToLogin` and `RedirectToAccessDenied` events as follows: |
| 53 | + |
| 54 | +```csharp |
| 55 | +builder.Services.AddAuthentication() |
| 56 | + .AddCookie(options => |
| 57 | + { |
| 58 | + options.Events.OnRedirectToLogin = context => |
| 59 | + { |
| 60 | + context.Response.Redirect(context.RedirectUri); |
| 61 | + return Task.CompletedTask; |
| 62 | + }; |
| 63 | + |
| 64 | + options.Events.OnRedirectToAccessDenied = context => |
| 65 | + { |
| 66 | + context.Response.Redirect(context.RedirectUri); |
| 67 | + return Task.CompletedTask; |
| 68 | + }; |
| 69 | + }); |
| 70 | +``` |
| 71 | + |
| 72 | +For more information about this breaking change, see https://github.com/aspnet/Announcements/issues/525 |
| 73 | + |
| 74 | +## Passkey authentication improvements |
| 75 | + |
| 76 | +APIs for passkey authentication in ASP.NET Core Identity have been updated and simplified. The Blazor Identity UI in the Blazor Web App project template has been updated accordingly. |
| 77 | + |
| 78 | +Passkey options are now configured globally via `IdentityPasskeyOptions`. Here's an example of how passkey options can be configured: |
| 79 | + |
| 80 | +```csharp |
| 81 | +builder.Services.Configure<IdentityPasskeyOptions>(options => |
| 82 | +{ |
| 83 | + // Explicitly set the Relying Party ID (domain) |
| 84 | + options.ServerDomain = "example.com"; |
| 85 | + |
| 86 | + // Configure authenticator timeout |
| 87 | + options.AuthenticatorTimeout = TimeSpan.FromMinutes(3); |
| 88 | + |
| 89 | + // Configure challenge size |
| 90 | + options.ChallengeSize = 64; |
| 91 | +}); |
| 92 | +``` |
| 93 | + |
| 94 | +The `SignInManager` passkey APIs have also been simplified. Passkey creation and request options can now be created as shown below: |
| 95 | + |
| 96 | +```csharp |
| 97 | +// Makes passkey options for use with the JS `navigator.credentials.create()` API: |
| 98 | +var optionsJson = await signInManager.MakePasskeyCreationOptionsAsync(new() |
| 99 | +{ |
| 100 | + Id = userId, |
| 101 | + Name = userName, |
| 102 | + DisplayName = displayName, |
| 103 | +}); |
| 104 | + |
| 105 | +// Makes passkey options for use with the JS `navigator.credentials.get()` API: |
| 106 | +var optionsJson = await signInManager.MakePasskeyRequestOptionsAsync(user); |
| 107 | +``` |
| 108 | + |
| 109 | +Below is an example of how a new passkey can be validated and added to a user: |
| 110 | + |
| 111 | +```csharp |
| 112 | +// 'credentialJson' is the JSON-serialized result from `navigator.credentials.create()`. |
| 113 | +var attestationResult = await SignInManager.PerformPasskeyAttestationAsync(); |
| 114 | +if (attestationResult.Succeeded) |
| 115 | +{ |
| 116 | + var addPasskeyResult = await UserManager.AddOrUpdatePasskeyAsync(user, attestationResult.Passkey); |
| 117 | + // ... |
| 118 | +} |
| 119 | +else { /* ... */ } |
| 120 | +``` |
| 121 | + |
| 122 | +To sign in with a passkey, use `SignInManager.PasskeySignInAsync()`: |
| 123 | +```csharp |
| 124 | +// 'credentialJson' is the JSON-serialized result from `navigator.credentials.get()`. |
| 125 | +var result = await signInManager.PasskeySignInAsync(credentialJson); |
| 126 | +``` |
| 127 | +### Getting started with passkeys |
| 128 | + |
| 129 | +**For new applications:** The Blazor Web App project template now includes passkey functionality out of the box. Create a new Blazor app with passkey support using: |
| 130 | + |
| 131 | +```sh |
| 132 | +dotnet new blazor -au Individual |
| 133 | +``` |
| 134 | + |
| 135 | +## Support for the .localhost top-level domain |
| 136 | + |
| 137 | +The `.localhost` top-level domain (TLD) is defined in [RFC2606](https://www.rfc-editor.org/rfc/rfc2606) and [RFC6761](https://www.rfc-editor.org/rfc/rfc6761) as being reserved for testing purposes and available for users to use locally as they would any other domain name. This means using a name like `myapp.localhost` locally that resolves to the IP loopback address is allowed and expected according to these RFCs. Additionally, modern evergreen browsers already automatically resolve any `*.localhost` name to the IP loopback address (`127.0.0.1`/`::1`), effectively making them an alias for any service already being hosted at `localhost` on the local machine. |
| 138 | + |
| 139 | +ASP.NET Core has been updated in .NET 10 preview 7 to better support the `.localhost` TLD, such that it can now be easily used when creating and running ASP.NET Core applications in your local development environment. Having different apps running locally be resolvable via different names allows for better separation of some domain-name-associated website assets, e.g. cookies, and makes it easier to identify which app you're browsing via the name displayed in the browser address bar. |
| 140 | + |
| 141 | +ASP.NET Core's built-in HTTP server, Kestrel, will now correctly treat any `*.localhost` name set via [supported endpoint configuration mechanisms](https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel/endpoints#configure-endpoints) as the local loopback address and thus bind to it rather than all external address (i.e. bind to `127.0.0.1`/`::1` rather than `0.0.0.0`/`::`). This includes the `"applicationUrl"` property in [launch profiles configured in a *launchSettings.json* file](https://learn.microsoft.com/aspnet/core/fundamentals/environments#development-and-launchsettingsjson), and the `ASPNETCORE_URLS` environment variable. When configured to listen on a `.localhost` address, Kestrel will log an information message for both the `.localhost` **and** `localhost` addresses, to make it clear that both names can be used. |
| 142 | + |
| 143 | +*Note that while web browsers will automatically resolve `*.localhost` names to the local loopback address, other applications may treat `*.localhost` names as a regular domain names and attempt to resolve them via their corresponding DNS stack. If your DNS configuration does not resolve `*.localhost` names to an address then they will fail to connect. You can continue to use the regular `localhost` name to address your applications when not in a web browser.* |
| 144 | + |
| 145 | +The [ASP.NET Core HTTPS development certificate](https://learn.microsoft.com/aspnet/core/security/enforcing-ssl#trust-the-aspnet-core-https-development-certificate) (including the `dotnet dev-certs https` command) have been updated to ensure the certificate is valid for use with the `*.dev.localhost` domain name. After installing .NET 10 SDK preview 7, trust the new developer certificate by running `dotnet dev-certs https --trust` at the command line to ensure your system is configured to trust the new certificate. |
| 146 | + |
| 147 | +*Note that the certificate lists the `*.dev.localhost` name as a Subject Alternative Name (SAN) rather than `*.localhost` as it's invalid to have wildcard certificates for top-level domain names* |
| 148 | + |
| 149 | +The project templates for *ASP.NET Core Empty* (`web`) and *Blazor Web App* (`blazor`) have been updated with a new option that when specified configures the created project to use the `.dev.localhost` domain name suffix, combining it with the project name to allow the app to be browsed to at an address like `https://myapp.dev.localhost:5036`: |
| 150 | + |
| 151 | +``` |
| 152 | +$ dotnet new web -n MyApp --localhost-tld |
| 153 | +The template "ASP.NET Core Empty" was created successfully. |
| 154 | +
|
| 155 | +Processing post-creation actions... |
| 156 | +Restoring D:\src\MyApp\MyApp.csproj: |
| 157 | +Restore succeeded. |
| 158 | +
|
| 159 | +$ cd .\MyApp\ |
| 160 | +$ dotnet run --launch-profile https |
| 161 | +info: Microsoft.Hosting.Lifetime[14] |
| 162 | + Now listening on: https://myapp.dev.localhost:7099 |
| 163 | +info: Microsoft.Hosting.Lifetime[14] |
| 164 | + Now listening on: https://localhost:7099/ |
| 165 | +info: Microsoft.Hosting.Lifetime[14] |
| 166 | + Now listening on: http://myapp.dev.localhost:5036 |
| 167 | +info: Microsoft.Hosting.Lifetime[14] |
| 168 | + Now listening on: http://localhost:5036/ |
| 169 | +info: Microsoft.Hosting.Lifetime[0] |
| 170 | + Application started. Press Ctrl+C to shut down. |
| 171 | +info: Microsoft.Hosting.Lifetime[0] |
| 172 | + Hosting environment: Development |
| 173 | +info: Microsoft.Hosting.Lifetime[0] |
| 174 | + Content root path: D:\src\local\10.0.1xx\MyApp |
| 175 | +``` |
| 176 | + |
| 177 | +## Use PipeReader support in System.Text.Json |
| 178 | + |
| 179 | +MVC, Minimal APIs, and the `HttpRequestJsonExtensions.ReadFromJsonAsync` methods have all been updated to use the new PipeReader support in System.Text.Json without requiring any code changes from applications. |
| 180 | + |
| 181 | +For the majority of applications this should have no impact on behavior. However, if the application is using a custom `JsonConverter`, there is a chance that the converter doesn't handle [Utf8JsonReader.HasValueSequence](https://learn.microsoft.com/dotnet/api/system.text.json.utf8jsonreader.hasvaluesequence) correctly. This can result in missing data and errors like `ArgumentOutOfRangeException` when deserializing. |
| 182 | + |
| 183 | +The quick workaround (especially if you don't own the custom `JsonConverter` being used) is to set the `"Microsoft.AspNetCore.UseStreamBasedJsonParsing"` [AppContext](https://learn.microsoft.com/dotnet/api/system.appcontext) switch to `true`. This should be a temporary workaround and the `JsonConverter`(s) should be updated to support `HasValueSequence`. |
| 184 | + |
| 185 | +To fix `JsonConverter` implementations, there is the quick fix which allocates an array from the `ReadOnlySequence`: |
| 186 | + |
| 187 | +```csharp |
| 188 | +public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
| 189 | +{ |
| 190 | + var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; |
| 191 | + // previous code |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +Or the more complicated (but performant) fix which would involve having a separate code path for the `ReadOnlySequence` handling: |
| 196 | + |
| 197 | +```csharp |
| 198 | +public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
| 199 | +{ |
| 200 | + if (reader.HasValueSequence) |
| 201 | + { |
| 202 | + reader.ValueSequence; |
| 203 | + // ReadOnlySequence optimized path |
| 204 | + } |
| 205 | + else |
| 206 | + { |
| 207 | + reader.ValueSpan; |
| 208 | + // ReadOnlySpan optimized path |
| 209 | + } |
| 210 | +} |
| 211 | +``` |
| 212 | + |
| 213 | +## Enhanced validation for classes and records |
| 214 | + |
| 215 | +Users can now use validation attributes on both classes and records, with consistent code generation and validation behavior. This enhances flexibility when designing models using records in ASP.NET Core applications. |
| 216 | + |
| 217 | +Thank you [@marcominerva](https://github.com/marcominerva) for this contribution! |
| 218 | + |
| 219 | +## Blazor improvements |
| 220 | + |
| 221 | +### Resource preloader component renamed |
| 222 | + |
| 223 | +The Blazor component for rendering preloading links has been renamed from `LinkPreload` to `ResourcePreloader`. |
| 224 | + |
| 225 | +### Updated API names for Blazor state persistence |
| 226 | + |
| 227 | +The new Blazor state persistence APIs have been updated: |
| 228 | + |
| 229 | +- Renamed JavaScript APIs: `Blazor.pause()` → `Blazor.pauseCircuit()` and `Blazor.resume()` → `Blazor.resumeCircuit()` |
| 230 | +- Renamed C# attribute: `SupplyParameterFromPersistentComponentStateAttribute` → `PersistentStateAttribute` |
| 231 | + |
| 232 | +### Support NotFound in custom Blazor routers |
| 233 | + |
| 234 | +Custom Blazor routers can now support `NavigationManager.NotFound()` by subscribing to the `NavigationManager.OnNotFound` event: |
| 235 | + |
| 236 | +```csharp |
| 237 | +private void OnNotFoundEvent(object sender, NotFoundEventArgs e) |
| 238 | +{ |
| 239 | + // Only execute the logic if HTTP response has started, |
| 240 | + // because setting args' Path blocks re-execution |
| 241 | + if (_httpContext?.Response.HasStarted == false) |
| 242 | + { |
| 243 | + return; |
| 244 | + } |
| 245 | + |
| 246 | + var type = typeof(CustomNotFoundPage); |
| 247 | + var routeAttributes = type.GetCustomAttributes(typeof(RouteAttribute), inherit: true); |
| 248 | + if (routeAttributes.Length == 0) |
| 249 | + { |
| 250 | + throw new InvalidOperationException($"The type {type.FullName} " + |
| 251 | + $"does not have a {typeof(RouteAttribute).FullName} applied to it."); |
| 252 | + } |
| 253 | + |
| 254 | + var routeAttribute = (RouteAttribute)routeAttributes[0]; |
| 255 | + if (routeAttribute.Template != null) |
| 256 | + { |
| 257 | + e.Path = routeAttribute.Template; |
| 258 | + } |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +### Updated metric names |
| 263 | + |
| 264 | +The Blazor diagnostic metrics have been updated to follow OpenTelemetry naming conventions: |
| 265 | + |
| 266 | +| Old | New | |
| 267 | +|----------|----------| |
| 268 | +| `aspnetcore.components.render_diff` | Split into `aspnetcore.components.render_diff.duration` and `aspnetcore.components.render_diff.size` | |
| 269 | +| `aspnetcore.components.navigation` | `aspnetcore.components.navigate` | |
| 270 | +| `aspnetcore.components.event_handler` | `aspnetcore.components.handle_event.duration` | |
| 271 | +| `aspnetcore.components.update_parameters` | `aspnetcore.components.update_parameters.duration` |
| 272 | + |
| 273 | +### Validate configured services for Blazor WebAssembly apps on build |
| 274 | + |
| 275 | +Previously, circular DI dependencies in Blazor WebAssembly apps would cause the browser to hang with no error message. Now developers get validation errors at build time instead of runtime hangs when running in development. |
| 276 | + |
| 277 | +To change the default service configuration validation behavior, use the new `UseDefaultServiceProvider` extension methods: |
| 278 | + |
| 279 | +```csharp |
| 280 | +builder.UseDefaultServiceProvider(options => |
| 281 | +{ |
| 282 | + options.ValidateOnBuild = false; |
| 283 | +}); |
| 284 | +``` |
| 285 | + |
| 286 | +## OpenAPI.NET dependency upgraded to stable release |
| 287 | + |
| 288 | +The ASP.NET Core OpenAPI document generation support has been upgraded to use the 2.0.0 stable release of the OpenAPI.NET library. No further breaking changes are expected in the OpenAPI document generation for this release. |
| 289 | + |
| 290 | +### Fix ProducesResponseType Description for Minimal APIs |
| 291 | + |
| 292 | +The Description property for the `ProducesResponseType` attribute is now correctly set in Minimal APIs even when the attribute type and the inferred return type are not an exact match. |
| 293 | + |
| 294 | +Thank you [@sander1095](https://github.com/sander1095) for this contribution! |
| 295 | + |
| 296 | +### Correct metadata type for formdata enum parameters |
| 297 | + |
| 298 | +The metadata type for formdata enum parameters in MVC controller actions has been updated to use the actual enum type instead of string. |
| 299 | + |
| 300 | +Thank you [@ascott18](https://github.com/ascott18) for this contribution! |
| 301 | + |
| 302 | +### Unify handling of documentation IDs in OpenAPI XML comment generator |
| 303 | + |
| 304 | +XML documentation comments from referenced assemblies are now correctly merged if their documentation IDs included return type suffixes. As a result, all valid XML comments are now reliably included in generated OpenAPI documentation, improving doc accuracy and completeness for APIs using referenced assemblies. |
| 305 | + |
| 306 | +## Contributors |
| 307 | + |
| 308 | +Thank you contributors! ❤️ |
| 309 | + |
| 310 | +- [@martincostello](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Amartincostello) |
| 311 | +- [@benhopkinstech](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Abenhopkinstech) |
| 312 | +- [@timdeschryver](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Atimdeschryver) |
| 313 | +- [@sander1095](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Asander1095) |
| 314 | +- [@marcominerva](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Amarcominerva) |
| 315 | +- [@jashook](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Ajashook) |
| 316 | +- [@shethaadit](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Ashethaadit) |
| 317 | +- [@ladeak](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Aladeak) |
| 318 | +- [@ascott18](https://github.com/dotnet/aspnetcore/pulls?q=is%3Apr+is%3Amerged+milestone%3A10.0-preview7+author%3Aascott18) |
0 commit comments