From b6eec36b6084bf5ec8694652f7ef7c93f7a65c05 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:11:24 -0400 Subject: [PATCH 01/15] Failure to preserve types used by a published app --- aspnetcore/blazor/forms/validation.md | 7 ++ .../host-and-deploy/configure-trimmer.md | 102 ++++++------------ 2 files changed, 37 insertions(+), 72 deletions(-) diff --git a/aspnetcore/blazor/forms/validation.md b/aspnetcore/blazor/forms/validation.md index 978e280e1800..6919ea24c56a 100644 --- a/aspnetcore/blazor/forms/validation.md +++ b/aspnetcore/blazor/forms/validation.md @@ -365,6 +365,13 @@ When validation messages are set in the component, they're added to the validato :::moniker-end +:::moniker range=">= aspnetcore-10.0" + +> [!NOTE] +> The approach in this section relies on an MVC controller for validation on the server. Although the approach is supported for all current and future versions of .NET, we plan to improve the approach by adopting [Minimal APIs](xref:fundamentals/minimal-apis) for server-side validation. The work is tracked by [Server validation with a validator component upgrade (`dotnet/AspNetCore.Docs` #36051)](https://github.com/dotnet/AspNetCore.Docs/issues/36051). + +:::moniker-end + Server validation is supported in addition to client validation: * Process client validation in the form with the component. diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index e369c66d8a72..2ab4ae0dcdd8 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -42,54 +42,20 @@ For more information, see [Trimming options (.NET documentation)](/dotnet/core/d ## Failure to preserve types used by a published app -Trimming may have detrimental effects for a published app leading to runtime errors. In apps that use [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/), the IL Trimmer often can't determine the required types for runtime reflection and trims them away or trims away parameter names from methods. This can happen with complex framework types used for JS interop, JSON serialization/deserialization, and other operations. +Trimming may have detrimental effects for a published app leading to runtime errors, even in spite of setting the [`` property](#configuration) to `false` in the project file. In apps that use [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/), the IL Trimmer often can't determine the required types for runtime reflection and trims them away or trims away parameter names from methods. This can happen with complex framework types used for JS interop, JSON serialization/deserialization, and other operations. The IL Trimmer is also unable to react to an app's dynamic behavior at runtime. To ensure the trimmed app works correctly once deployed, test published output frequently while developing. -Consider the following client-side component in a Blazor Web App (.NET 8 or later) that deserializes a collection (`List>`): - -```razor -@rendermode @(new InteractiveWebAssemblyRenderMode(false)) -@using System.Diagnostics.CodeAnalysis -@using System.Text.Json - -
- @foreach (var item in @items) - { -
@item.Key
-
@item.Value
- } -
- -@code { - private List> items = []; - - [StringSyntax(StringSyntaxAttribute.Json)] - private const string data = - """[{"key":"key 1","value":"value 1"},{"key":"key 2","value":"value 2"}]"""; - - protected override void OnInitialized() - { - JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; - - items = JsonSerializer - .Deserialize>>(data, options)!; - } -} -``` - -The preceding component executes normally when the app is run locally and produces the following rendered definition list (`
`): +To address lost types, consider the following approaches. -> **:::no-loc text="key 1":::** -> :::no-loc text="value 1"::: -> **:::no-loc text="key 2":::** -> :::no-loc text="value 2"::: +### Custom types -When the app is published, is trimmed from the app, even in spite of setting the [`` property](#configuration) to `false` in the project file. Accessing the component throws the following exception: +Custom types aren't trimmed by Blazor when an app is published, so we recommend using custom types for JS interop, JSON serialization/deserialization, and other operations that rely on reflection. -> :::no-loc text="Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String]"::: +If you prefer to use framework types in spite of our recommendation, use either of the following approaches: -To address lost types, consider the following approaches. +* [Preserve the type as a dynamic dependency](#preserve-the-type-as-a-dynamic-dependency) +* [Use a Root Descriptor](#use-a-root-descriptor) ### Preserve the type as a dynamic dependency @@ -101,6 +67,11 @@ If not already present, add an `@using` directive for + Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute) to preserve the : ```diff @@ -108,14 +79,22 @@ Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.Dyn private List> items = []; ``` - + A [Root Descriptor](/dotnet/core/deploying/trimming/trimming-options#root-descriptors) can preserve the type. Add an `ILLink.Descriptors.xml` file to the root of the app† with the type: + + ```xml @@ -138,42 +117,21 @@ Add a `TrimmerRootDescriptor` item to the app's project file‡ referencing ‡The project file is either the project file of the Blazor WebAssembly app or the project file of the `.Client` project of a Blazor Web App (.NET 8 or later). ---> +:::moniker-end -### Custom types +:::moniker range="= aspnetcore-8.0" - - -The following modifications create a `StringKeyValuePair` type for use by the component. - -`StringKeyValuePair.cs`: - -```csharp -[method: SetsRequiredMembers] -public sealed class StringKeyValuePair(string key, string value) -{ - public required string Key { get; init; } = key; - public required string Value { get; init; } = value; -} -``` - -The component is modified to use the `StringKeyValuePair` type: - -```diff -- private List> items = []; -+ private List items = []; -``` - -```diff -- items = JsonSerializer.Deserialize>>(data, options)!; -+ items = JsonSerializer.Deserialize>(data, options)!; +```xml + + <_ExtraTrimmerArgs>--keep-metadata parametername + ``` -Because custom types are never trimmed by Blazor when an app is published, the component works as designed after the app is published. +:::moniker-end ## Additional resources From ab140171699c8f00a1195aa585c5e2f14d58f50a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:13:50 -0400 Subject: [PATCH 02/15] Updates --- aspnetcore/blazor/javascript-interoperability/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/javascript-interoperability/index.md b/aspnetcore/blazor/javascript-interoperability/index.md index 297b16dd8aed..30f0f22f5fbf 100644 --- a/aspnetcore/blazor/javascript-interoperability/index.md +++ b/aspnetcore/blazor/javascript-interoperability/index.md @@ -202,7 +202,7 @@ Blazor uses for serialization w * Global default serialization isn't customizable to avoid breaking existing component libraries, impacts on performance and security, and reductions in reliability. * Serializing .NET member names results in lowercase JSON key names. * JSON is deserialized as C# instances, which permit mixed casing. Internal casting for assignment to C# model properties works as expected in spite of any case differences between JSON key names and C# property names. -* Complex framework types, such as , might be [trimmed away by the IL Trimmer on publish](xref:blazor/host-and-deploy/configure-trimmer#failure-to-preserve-types-used-by-a-published-app) and not present for JS interop or JSON serialization/deserialization. We recommend creating custom types for types that the IL Trimmer trims away. +* Complex framework types might be [trimmed away by the IL Trimmer on publish](xref:blazor/host-and-deploy/configure-trimmer#failure-to-preserve-types-used-by-a-published-app) and not present for JS interop or JSON serialization/deserialization. We recommend creating custom types for types that the IL Trimmer trims away. * Blazor always relies on [reflection for JSON serialization](/dotnet/standard/serialization/system-text-json/reflection-vs-source-generation), including when using C# [source generation](/dotnet/csharp/roslyn-sdk/source-generators-overview). Setting `JsonSerializerIsReflectionEnabledByDefault` to `false` in the app's project file results in an error when serialization is attempted. API is available for custom serialization. Properties can be annotated with a [`[JsonConverter]` attribute](xref:System.Text.Json.Serialization.JsonConverterAttribute) to override default serialization for an existing data type. From 54af8d0413becd1ea17e9f264ba33d59ee230719 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:27:23 -0400 Subject: [PATCH 03/15] Updates --- aspnetcore/blazor/host-and-deploy/configure-trimmer.md | 2 +- aspnetcore/release-notes/aspnetcore-10/includes/blazor.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 2ab4ae0dcdd8..9da47b3db979 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -5,7 +5,7 @@ description: Learn how to control the Intermediate Language (IL) Trimmer when bu monikerRange: '>= aspnetcore-5.0' ms.author: wpickett ms.custom: mvc -ms.date: 11/12/2024 +ms.date: 09/04/2025 uid: blazor/host-and-deploy/configure-trimmer --- # Configure the Trimmer for ASP.NET Core Blazor diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 02638cd2a706..c362e076c42e 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -708,3 +708,10 @@ In the following example, a hidden input field is created for the form's `Parame private void Submit() => submitted = true; } ``` + +### Preservation of types used by a published app + +We remain committed to our recommendation on using custom types for JS interop, JSON serialization/deserialization, and other operations that rely on [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/). However, using framework types that ordinarily are trimmed when publishing an app remains supported with the following approaches: + +* [Preserve the type as a dynamic dependency](xref:blazor/host-and-deploy/configure-trimmer#preserve-the-type-as-a-dynamic-dependency): Supported since .NET 5. +* [Use a Root Descriptor](xref:blazor/host-and-deploy/configure-trimmer#use-a-root-descriptor): *A new approach for apps targeting .NET 10 or later.* From 42419cea66ebae95b739936e5b98b27c16ac5416 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:23:33 -0400 Subject: [PATCH 04/15] Updates --- aspnetcore/blazor/forms/validation.md | 30 ++++---- aspnetcore/fundamentals/minimal-apis.md | 28 ++++---- aspnetcore/mvc/models/validation.md | 54 +++++++++++--- aspnetcore/release-notes/aspnetcore-10.0.md | 72 +++++++++++++++++++ .../aspnetcore-10/includes/blazor.md | 15 +++- 5 files changed, 161 insertions(+), 38 deletions(-) diff --git a/aspnetcore/blazor/forms/validation.md b/aspnetcore/blazor/forms/validation.md index 6919ea24c56a..06f9698bb870 100644 --- a/aspnetcore/blazor/forms/validation.md +++ b/aspnetcore/blazor/forms/validation.md @@ -134,6 +134,18 @@ The compon * [`DataAnnotationsValidator`](https://github.com/dotnet/AspNetCore/blob/main/src/Components/Forms/src/DataAnnotationsValidator.cs) * [`EnableDataAnnotationsValidation`](https://github.com/dotnet/AspNetCore/blob/main/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs) +:::moniker range=">= aspnetcore-10.0" + +The component has the same validation order and short-circuiting behavior as . The following rules are applied when validating an instance of type `T`: + +1. Member properties of `T` are validated, including recursively validating nested objects. +1. Type-level attributes of `T` are validated. +1. The method is executed, if `T` implements it. + +If one of the preceding steps produces a validation error, the remaining steps are skipped. + +:::moniker-end + If you need to enable data annotations validation support for an in code, call with an injected (`@inject IServiceProvider ServiceProvider`) on the . For an advanced example, see the [`NotifyPropertyChangedValidationComponent` component in the ASP.NET Core Blazor framework's `BasicTestApp` (`dotnet/aspnetcore` GitHub repository)](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/FormsTest/NotifyPropertyChangedValidationComponent.razor). In a production version of the example, replace the `new TestServiceProvider()` argument for the service provider with an injected . [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] @@ -1645,26 +1657,18 @@ In the following `OrderPage` component, the . However, the only validates top-level properties of the model bound to the form that aren't complex-type properties. - -To validate the bound model's entire object graph, including complex-type properties, use the `ObjectGraphDataAnnotationsValidator` provided by the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package. - -> [!NOTE] -> The `ObjectGraphDataAnnotationsValidator` isn't compatible with [nested objects and collection types validation](#nested-objects-and-collection-types), but it's capable of validating nested objects and collection types on its own. - :::moniker-end :::moniker range="< aspnetcore-10.0" ## Nested objects, collection types, and complex types -Blazor provides support for validating form input using data annotations with the built-in . However, the only validates top-level properties of the model bound to the form that aren't collection- or complex-type properties. +> [!NOTE] +> For apps targeting .NET 10 or later, we no longer recommend the use of the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package and approach described in this section. We recommend using the built-in validation features of the the component, which received feature parity and behavioral compatibility updates with with the release of .NET 10. For more information, see . -To validate the bound model's entire object graph, including collection- and complex-type properties, use the `ObjectGraphDataAnnotationsValidator` provided by the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package: +Blazor provides support for validating form input using data annotations with the built-in . However, the in .NET 9 or earlier only validates top-level properties of the model bound to the form that aren't collection- or complex-type properties. -:::moniker-end +To validate the bound model's entire object graph, including collection- and complex-type properties, use the `ObjectGraphDataAnnotationsValidator` provided by the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package in .NET 9 or earlier: ```razor @@ -1710,6 +1714,8 @@ public class ShipDescription } ``` +:::moniker-end + ## Enable the submit button based on form validation To enable and disable the submit button based on form validation, the following example: diff --git a/aspnetcore/fundamentals/minimal-apis.md b/aspnetcore/fundamentals/minimal-apis.md index 9c2a86ff3291..5d407a657794 100644 --- a/aspnetcore/fundamentals/minimal-apis.md +++ b/aspnetcore/fundamentals/minimal-apis.md @@ -1,7 +1,7 @@ --- title: Minimal APIs quick reference author: wadepickett -description: Provides an overview of minimal APIs in ASP.NET Core +description: Provides an overview of Minimal APIs in ASP.NET Core ms.author: wpickett content_well_notification: AI-contribution monikerRange: '>= aspnetcore-6.0' @@ -20,19 +20,19 @@ ai-usage: ai-assisted This document: -* Provides a quick reference for minimal APIs. +* Provides a quick reference for Minimal APIs. * Is intended for experienced developers. For an introduction, see . -The minimal APIs consist of: +The Minimal APIs consist of: -* [WebApplication and WebApplicationBuilder](xref:fundamentals/minimal-apis/webapplication) +* [`WebApplication` and `WebApplicationBuilder`](xref:fundamentals/minimal-apis/webapplication) * [Route Handlers](xref:fundamentals/minimal-apis/route-handlers) [!INCLUDE[](~/fundamentals/minimal-apis/includes/webapplication10.md)] ## ASP.NET Core Middleware -The following table lists some of the middleware frequently used with minimal APIs. +The following table lists some of the middleware frequently used with Minimal APIs. | Middleware | Description | API | |--|--|--| @@ -72,7 +72,7 @@ The arguments passed to these methods are called "route h ## Validation support in Minimal APIs -Support for validation in Minimal APIs is now available. This feature allows you to request validation of data sent to your API endpoints. Enabling validation allows the ASP.NET Core runtime to perform any validations defined on the: +Enabling validation allows the ASP.NET Core runtime to perform validations defined on the: * Query * Header @@ -89,20 +89,20 @@ public record Product( ``` Developers customize the behavior of the validation system by: -* Creating custom [`[Validation]`](xref:System.ComponentModel.DataAnnotations.ValidationAttribute) attribute implementations. +* Creating custom [`[Validation]` attribute](xref:System.ComponentModel.DataAnnotations.ValidationAttribute) implementations. * Implementing the [`IValidatableObject`](xref:System.ComponentModel.DataAnnotations.IValidatableObject) interface for complex validation logic. -If validation fails, the runtime returns a 400 Bad Request response with details of the validation errors. +If validation fails, the runtime returns a *400 - Bad Request* response with details of the validation errors. -### Enable built-in validation support for minimal APIs +### Enable built-in validation support for Minimal APIs -Enable the built-in validation support for minimal APIs by calling the `AddValidation` extension method to register the required services in the service container for your application: +Enable the built-in validation support for Minimal APIs by calling the `AddValidation` extension method to register the required services in the service container for your application: ```csharp builder.Services.AddValidation(); ``` -The implementation automatically discovers types that are defined in minimal API handlers or as base types of types defined in minimal API handlers. An endpoint filter performs validation on these types and is added for each endpoint. +The implementation automatically discovers types that are defined in Minimal API handlers or as base types of types defined in Minimal API handlers. An endpoint filter performs validation on these types and is added for each endpoint. Validation can be disabled for specific endpoints by using the `DisableValidation` extension method, as in the following example: @@ -114,7 +114,7 @@ app.MapPost("/products", ``` ### Customize validation error responses using IProblemDetailsService -Customize error responses from minimal API validation logic with an implementation. Register this service in your application's service collection to enable more consistent and user-specific error responses. Support for minimal API validation was introduced in ASP.NET Core in .NET 10. +Customize error responses from Minimal API validation logic with an implementation. Register this service in your application's service collection to enable more consistent and user-specific error responses. Support for Minimal API validation was introduced in ASP.NET Core in .NET 10. To implement custom validation error responses: @@ -253,7 +253,7 @@ We recommend adding an extension method to interface can represent values returned from minimal APIs that don't utilize the implicit support for JSON serializing the returned object to the HTTP response. The static [Results](/dotnet/api/microsoft.aspnetcore.http.results) class is used to create varying `IResult` objects that represent different types of responses. For example, setting the response status code or redirecting to another URL. +The interface can represent values returned from Minimal APIs that don't utilize the implicit support for JSON serializing the returned object to the HTTP response. The static [Results](/dotnet/api/microsoft.aspnetcore.http.results) class is used to create varying `IResult` objects that represent different types of responses. For example, setting the response status code or redirecting to another URL. The types implementing `IResult` are public, allowing for type assertions when testing. For example: @@ -333,7 +333,7 @@ The following code disables `ValidateScopes` and `ValidateOnBuild` in `Developme * [Short-circuit routing](https://andrewlock.net/exploring-the-dotnet-8-preview-short-circuit-routing/) * [Identity API endpoints](https://andrewlock.net/exploring-the-dotnet-8-preview-introducing-the-identity-api-endpoints/) * [Keyed service dependency injection container support](https://andrewlock.net/exploring-the-dotnet-8-preview-keyed-services-dependency-injection-support/) -* [A look behind the scenes of minimal API endpoints](https://andrewlock.net/behind-the-scenes-of-minimal-apis-1-a-first-look-behind-the-scenes-of-minimal-api-endpoints/) +* [A look behind the scenes of Minimal API endpoints](https://andrewlock.net/behind-the-scenes-of-minimal-apis-1-a-first-look-behind-the-scenes-of-minimal-api-endpoints/) * [Organizing ASP.NET Core Minimal APIs](https://www.tessferrandez.com/blog/2023/10/31/organizing-minimal-apis.html) * [Fluent validation discussion on GitHub](https://github.com/dotnet/aspnetcore/issues/51834#issuecomment-1837180853) diff --git a/aspnetcore/mvc/models/validation.md b/aspnetcore/mvc/models/validation.md index a29e3fb61e16..e21ead21a8ed 100644 --- a/aspnetcore/mvc/models/validation.md +++ b/aspnetcore/mvc/models/validation.md @@ -44,22 +44,54 @@ Validation attributes let you specify validation rules for model properties. The ## Built-in attributes +For a complete list of validation attributes, see the namespace. + + + ### Error messages Validation attributes let you specify the error message to be displayed for invalid input. For example: diff --git a/aspnetcore/release-notes/aspnetcore-10.0.md b/aspnetcore/release-notes/aspnetcore-10.0.md index d1570ecc1523..b11060f7b5e7 100644 --- a/aspnetcore/release-notes/aspnetcore-10.0.md +++ b/aspnetcore/release-notes/aspnetcore-10.0.md @@ -130,6 +130,78 @@ if (RedirectHttpResult.IsLocalUrl(url)) Thank you [@martincostello](https://github.com/martincostello) for this contribution! +### Validation improvements for Blazor and Minimal APIs + +Several features and fixes have been added to the new validation API for Minimal APIs and Blazor, introducing feature parity and behavioral compatibility with the existing . + +#### Type-level validation attributes + +Validation now supports attributes placed on classes and records. + +Consider the following attribute to validate a sum limit: + +```csharp +class SumLimitAttribute(int Limit) : ValidationAttribute +{ + protected override ValidationResult? IsValid(object? value, ValidationContext _) + { + if (value is Point point && point.X + point.Y > Limit) + { + return new ValidationResult("The sum of X and Y is too high"); + } + + return ValidationResult.Success; + } +} +``` + +The attribute can now be placed on a record: + +```csharp +[SumLimit(42)] +record Point(int X, int Y); +``` + +#### Skip validation + +Use the new `[SkipValidation]` attribute to omit selected properties, parameters, or types from validation. When applied to a property or a method parameter, the validator skips that value during validation. When applied to a type, the validator skips all properties and parameters of that type. + +This can be useful, in particular, when using the same model types in cases that require and don't require validation. + +In the following example, validation is skipped for `ContactAddress` by applying the `[SkipValidation]` attribute to its property in the `Order` class, in spite of `Address.Street` normally requiring a value: + +```csharp +class Address +{ + [Required] + public string Street { get; set; } + + // ... +} + +class Order +{ + public Address PaymentAddress { get; set; } + + [SkipValidation] + public Address ContactAddress { get; set; } + + // ... +} +``` + +Additionally, properties annotated with the [`[JsonIgnore]` attribute](xref:System.Text.Json.Serialization.JsonIgnoreAttribute) are now also omitted from validation to improve consistency between serialization and validation in the context of JSON models. Note that the `[SkipValidation]` attribute should be preferred in general cases. + +#### Backwards-compatible behavior + +Type validation logic has been updated to match the validation order and short-circuiting behavior of . This means that the following rules are applied when validating an instance of type `T`: + +1. Member properties of `T` are validated, including recursively validating nested objects. +1. Type-level attributes of `T` are validated. +1. The method is executed, if `T` implements it. + +If one of the preceding steps produces a validation error, the remaining steps are skipped. + ## Breaking changes Use the articles in [Breaking changes in .NET](/dotnet/core/compatibility/breaking-changes) to find breaking changes that might apply when upgrading an app to a newer version of .NET. diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index c362e076c42e..05f9428b7121 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -587,6 +587,19 @@ In the following `OrderPage` component, the component now has the same validation order and short-circuiting behavior as . The following rules are applied when validating an instance of type `T`: + +1. Member properties of `T` are validated, including recursively validating nested objects. +1. Type-level attributes of `T` are validated. +1. The method is executed, if `T` implements it. + +If one of the preceding steps produces a validation error, the remaining steps are skipped. + ### Custom Blazor cache and `BlazorCacheBootResources` MSBuild property removed Now that all Blazor client-side files are fingerprinted and cached by the browser, Blazor's custom caching mechanism and the `BlazorCacheBootResources` MSBuild property have been removed from the framework. If the client-side project's project file contains the MSBuild property, remove the property, as it no longer has any effect: @@ -711,7 +724,7 @@ In the following example, a hidden input field is created for the form's `Parame ### Preservation of types used by a published app -We remain committed to our recommendation on using custom types for JS interop, JSON serialization/deserialization, and other operations that rely on [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/). However, using framework types that ordinarily are trimmed when publishing an app remains supported with the following approaches: +We continue to recommend use of custom types for JS interop, JSON serialization/deserialization, and other operations that rely on [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/). However, using framework types that ordinarily are trimmed when publishing an app remains supported with the following approaches: * [Preserve the type as a dynamic dependency](xref:blazor/host-and-deploy/configure-trimmer#preserve-the-type-as-a-dynamic-dependency): Supported since .NET 5. * [Use a Root Descriptor](xref:blazor/host-and-deploy/configure-trimmer#use-a-root-descriptor): *A new approach for apps targeting .NET 10 or later.* From 76a656899c9777ade65fc93577a8ca9659d73e76 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:33:16 -0400 Subject: [PATCH 05/15] Updates --- aspnetcore/mvc/models/validation.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/aspnetcore/mvc/models/validation.md b/aspnetcore/mvc/models/validation.md index e21ead21a8ed..027dcdb9d95d 100644 --- a/aspnetcore/mvc/models/validation.md +++ b/aspnetcore/mvc/models/validation.md @@ -48,12 +48,15 @@ For a complete list of validation attributes, see the namespace. --> From 5c7cc79c9785f76f38e5ac48ed2a7e168fd1b175 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:35:43 -0400 Subject: [PATCH 06/15] Updates --- .../host-and-deploy/configure-trimmer.md | 98 ++++++++++++++++--- 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 9da47b3db979..8c03066ee131 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -46,12 +46,85 @@ Trimming may have detrimental effects for a published app leading to runtime err The IL Trimmer is also unable to react to an app's dynamic behavior at runtime. To ensure the trimmed app works correctly once deployed, test published output frequently while developing. -To address lost types, consider the following approaches. +Consider the following example that performs JSON deserialization into a collection (`List>`). + +`TrimExample.razor`: + +```razor +@page "/trim-example" +@using System.Diagnostics.CodeAnalysis +@using System.Text.Json + +

Trim Example

+ +
    + @foreach (var item in @items) + { +
  • @item.Item1, @item.Item2
  • + } +
+ +@code { + private List> items = []; + + [StringSyntax(StringSyntaxAttribute.Json)] + private const string data = + """[{"item1":"1:T1","item2":"1:T2"},{"item1":"2:T1","item2":"2:T2"}]"""; + + protected override void OnInitialized() + { + JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; + + items = JsonSerializer + .Deserialize>>(data, options)!; + } +} +``` + +The preceding component executes normally when the app is run locally and produces the following rendered list: + +> • 1:T1, 1:T2 +> • 2:T2, 2:T2 + +When the app is published, is trimmed from the app, even in spite of setting the `` property to `false` in the project file. Accessing the component throws the following exception: + +> :::no-loc text="crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]"::: +> :::no-loc text="Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Tuple`2[System.String,System.String]"::: +> :::no-loc text="System.NotSupportedException: ConstructorContainsNullParameterNames, System.Tuple`2[System.String,System.String]"::: + +To address lost types, consider adopting one of the following approaches. ### Custom types Custom types aren't trimmed by Blazor when an app is published, so we recommend using custom types for JS interop, JSON serialization/deserialization, and other operations that rely on reflection. +The following modifications create a StringKeyValuePair type for use by the component. + +`StringTuple.cs`: + +```csharp +[method: SetsRequiredMembers] +public sealed class StringTuple(string item1, string item2) +{ + public required string Item1 { get; init; } = item1; + public required string Item2 { get; init; } = item2; +} +``` + +The component is modified to use the StringKeyValuePair type: + +```diff +- private List> items = []; ++ private List items = []; +``` + +```diff +- items = JsonSerializer.Deserialize>>(data, options)!; ++ items = JsonSerializer.Deserialize>(data, options)!; +``` + +Because custom types are never trimmed by Blazor when an app is published, the component works as designed after the app is published. + If you prefer to use framework types in spite of our recommendation, use either of the following approaches: * [Preserve the type as a dynamic dependency](#preserve-the-type-as-a-dynamic-dependency) @@ -67,16 +140,12 @@ If not already present, add an `@using` directive for - -Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute) to preserve the : +Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute) to preserve the : ```diff -+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(KeyValuePair))] -private List> items = []; ++ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, ++ typeof(Tuple))] +private List> items = []; ``` :::moniker range=">= aspnetcore-10.0" @@ -86,21 +155,18 @@ private List> items = []; A [Root Descriptor](/dotnet/core/deploying/trimming/trimming-options#root-descriptors) can preserve the type. Add an `ILLink.Descriptors.xml` file to the root of the app† with the type: - - ```xml - - - - + + ``` From c738abac8efb85d4f3743e1a834c4315687658ff Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 5 Sep 2025 12:33:24 -0400 Subject: [PATCH 07/15] Updates --- .../blazor/host-and-deploy/configure-trimmer.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 8c03066ee131..a679c9b93268 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -98,7 +98,7 @@ To address lost types, consider adopting one of the following approaches. Custom types aren't trimmed by Blazor when an app is published, so we recommend using custom types for JS interop, JSON serialization/deserialization, and other operations that rely on reflection. -The following modifications create a StringKeyValuePair type for use by the component. +The following modifications create a `StringTuple` type for use by the component. `StringTuple.cs`: @@ -111,7 +111,7 @@ public sealed class StringTuple(string item1, string item2) } ``` -The component is modified to use the StringKeyValuePair type: +The component is modified to use the `StringTuple` type: ```diff - private List> items = []; @@ -152,13 +152,6 @@ private List> items = []; ### Use a Root Descriptor - - A [Root Descriptor](/dotnet/core/deploying/trimming/trimming-options#root-descriptors) can preserve the type. Add an `ILLink.Descriptors.xml` file to the root of the app† with the type: From 5705ecf7b95f456f620582a7154839b3ba4f3f77 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:02:30 -0400 Subject: [PATCH 08/15] Updates --- aspnetcore/blazor/fundamentals/routing.md | 29 +++++++++++++++++++ .../migration/90-to-100/includes/blazor.md | 27 +++++++++++++++++ .../aspnetcore-10/includes/blazor.md | 4 +-- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/routing.md b/aspnetcore/blazor/fundamentals/routing.md index cee45dab7dba..fd7ec4f2eeff 100644 --- a/aspnetcore/blazor/fundamentals/routing.md +++ b/aspnetcore/blazor/fundamentals/routing.md @@ -704,6 +704,35 @@ The following component: For more information on component disposal, see . +:::moniker range=">= aspnetcore-9.0" + +## Navigation Manager redirect behavior during static server-side rendering (static SSR) + +:::moniker-end + +:::moniker range=">= aspnetcore-10.0" + +In .NET 9 for a redirect during static server-side rendering (static SSR), relied on throwing an exception that was captured by the framework, which converted the error into a redirect. Code that existed after the call to abruptly stopped, which also didn't match the behavior during interactive rendering. When using Visual Studio, the debugger stopped on the exception, which required deselecting the checkbox for **Break when this exception type is user-handled** in the Visual Studio UI to avoid the debugger stopping for future redirects. + +In .NET 10 or later, the logic for redirects during static SSR no longer throws an exception, so the behavior for static SSR matches the behavior for interactive render modes. Visual Studio no longer breaks on an exception. To revert to the previous behavior of throwing a in .NET 10 or later, set the following switch in the `Program` file: + +```csharp +AppContext.SetSwitch( + "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", + isEnabled: false); +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-9.0 < aspnetcore-10.0" + +For a redirect during static server-side rendering (static SSR), relies on throwing an exception that gets captured by the framework, which converts the error into a redirect. Code that exists after the call to isn't called. When using Visual Studio, the debugger breaks on the exception, requiring you to deselect the checkbox for **Break when this exception type is user-handled** in the VS UI to avoid the debugger stopping for future redirects. + +> [!NOTE] +> The preceding behavior was updated at the release of .NET 10 to avoid throwing an exception. To take advantage of the new behavior, upgrade the app to .NET 10 or later. + +:::moniker-end + :::moniker range=">= aspnetcore-10.0" ## Not Found responses diff --git a/aspnetcore/migration/90-to-100/includes/blazor.md b/aspnetcore/migration/90-to-100/includes/blazor.md index 4598252c3211..de5841944511 100644 --- a/aspnetcore/migration/90-to-100/includes/blazor.md +++ b/aspnetcore/migration/90-to-100/includes/blazor.md @@ -3,3 +3,30 @@ Complete migration coverage for Blazor apps is scheduled for September and Octob ### Adopt passkey user authentication in an existing Blazor Web App For guidance, see . + +### Update the `IdentityRedirectManager` in apps based on the Blazor Web App template with Individual Accounts + +The `IdentityRedirectManager` previously threw an in the `RedirectTo` method to ensure the method wasn't called from an interactive render mode and all the redirection methods were marked with the [`[DoesNotReturn]` attribute](xref:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute). For more information, see . + +In `Components/Account/IdentityRedirectManager.cs`: + +* Remove the from the `RedirectTo` method: + + ```diff + - throw new InvalidOperationException( + - $"{nameof(IdentityRedirectManager)} can only be used during static rendering."); + ``` + +* Remove five instances of the the `[DoesNotReturn]` attribute from the file: + + ```diff + - [DoesNotReturn] + ``` + +Alternatively, to revert to the previous behavior of throwing a , set the following switch in the `Program` file: + +```csharp +AppContext.SetSwitch( + "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", + isEnabled: false); +``` diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 05f9428b7121..d7ba7abf6364 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -374,13 +374,13 @@ For more information, see during static server-side rendering (SSR) would throw a , interrupting execution before being converted to a redirection response. This caused confusion during debugging and was inconsistent with interactive rendering, where code after continues to execute normally. +Previously, calling during static server-side rendering (static SSR) would throw a , interrupting execution before being converted to a redirection response. This caused confusion during debugging and was inconsistent with interactive rendering, where code after continues to execute normally. Calling during static SSR no longer throws a . Instead, it behaves consistently with interactive rendering by performing the navigation without throwing an exception. Code that relied on being thrown should be updated. For example, in the default Blazor Identity UI, the `IdentityRedirectManager` previously threw an after calling `RedirectTo` to ensure it wasn't invoked during interactive rendering. This exception and the [`[DoesNotReturn]` attributes](xref:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute) should now be removed. -To revert to the previous behavior of throwing a , set the following switch: +To revert to the previous behavior of throwing a , set the following switch in the `Program` file: ```csharp AppContext.SetSwitch( From 62e97f28a43c9271104dc92fd8659b4d5bebd206 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:37:51 -0400 Subject: [PATCH 09/15] Updates --- .../prerendered-state-persistence.md | 48 ++++++++++++++++--- .../aspnetcore-10/includes/blazor.md | 34 +++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/aspnetcore/blazor/state-management/prerendered-state-persistence.md b/aspnetcore/blazor/state-management/prerendered-state-persistence.md index 6114ad55b287..c6083442686a 100644 --- a/aspnetcore/blazor/state-management/prerendered-state-persistence.md +++ b/aspnetcore/blazor/state-management/prerendered-state-persistence.md @@ -312,13 +312,47 @@ For components embedded into a page or view of a Razor Pages or MVC app, you mus ## Interactive routing and prerendering + + When the `Routes` component doesn't define a render mode, the app is using per-page/component interactivity and navigation. Using per-page/component navigation, internal navigation is handled by [enhanced routing](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling) after the app becomes interactive. "Internal navigation" in this context means that the URL destination of the navigation event is a Blazor endpoint inside the app. - +:::moniker range=">= aspnetcore-10.0" + +Blazor supports handling persistent component state during [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling). State persisted during enhanced navigation can be read by interactive components on the page. + +By default, persistent component state is only loaded by interactive components when they're initially loaded on the page. This prevents important state, such as data in an edited webform, from being overwritten if additional enhanced navigation events occur to the same page after the component is loaded. + +If the data is read-only and doesn't change frequently, you can opt-in to allow updates during enhanced navigation by setting `AllowUpdates = true` on the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute). This is useful for scenarios such as displaying cached data that's expensive to fetch but doesn't change often. The following example demonstrates the use of `AllowUpdates` for weather forecast data: + +```csharp +[PersistentState(AllowUpdates = true)] +public WeatherForecast[]? Forecasts { get; set; } + +protected override async Task OnInitializedAsync() +{ +    Forecasts ??= await ForecastService.GetForecastAsync(); +} +``` + +To skip restoring state during prerendering, set `RestoreBehavior` to `SkipInitialValue`: + +```csharp +[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)] +public string NoPrerenderedData { get; set; } +``` + +To skip restoring state during reconnection, set `RestoreBehavior` to `SkipLastSnapshot`. This can be useful if you want to ensure fresh data after reconnection: + +```csharp +[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)] +public int CounterNotRestoredOnReconnect { get; set; } +``` + +Call `PersistentComponentState.RegisterOnRestoring` to register a callback for imperatively controlling how state is restored, similar to how provides full control of how state is persisted. + +:::moniker-end + +:::moniker range="< aspnetcore-10.0" The service only works on the initial page load and not across internal enhanced page navigation events. @@ -326,4 +360,6 @@ If the app performs a full (non-enhanced) navigation to a page utilizing persist If an interactive circuit has already been established and an enhanced navigation is performed to a page utilizing persistent component state, the state *isn't made available in the existing circuit for the component to use*. There's no prerendering for the internal page request, and the service isn't aware that an enhanced navigation has occurred. There's no mechanism to deliver state updates to components that are already running on an existing circuit. The reason for this is that Blazor only supports passing state from the server to the client at the time the runtime initializes, not after the runtime has started. -Disabling enhanced navigation, which reduces performance but also avoids the problem of loading state with for internal page requests, is covered in . +Disabling enhanced navigation, which reduces performance but also avoids the problem of loading state with for internal page requests, is covered in . Alternatively, update the app to .NET 10 or later, where Blazor supports handling persistent component state when during enhanced navigation. + +:::moniker-end diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index d7ba7abf6364..5626be602de1 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -728,3 +728,37 @@ We continue to recommend use of custom types for JS interop, JSON serialization/ * [Preserve the type as a dynamic dependency](xref:blazor/host-and-deploy/configure-trimmer#preserve-the-type-as-a-dynamic-dependency): Supported since .NET 5. * [Use a Root Descriptor](xref:blazor/host-and-deploy/configure-trimmer#use-a-root-descriptor): *A new approach for apps targeting .NET 10 or later.* + +### Persistent component state support for enhanced navigation + +Blazor now supports handling persistent component state during [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling). State persisted during enhanced navigation can be read by interactive components on the page. + +By default, persistent component state is only loaded by interactive components when they're initially loaded on the page. This prevents important state, such as data in an edited webform, from being overwritten if additional enhanced navigation events occur to the same page after the component is loaded. + +If the data is read-only and doesn't change frequently, you can opt-in to allow updates during enhanced navigation by setting `AllowUpdates = true` on the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute). This is useful for scenarios such as displaying cached data that's expensive to fetch but doesn't change often. The following example demonstrates the use of `AllowUpdates` for weather forecast data: + +```csharp +[PersistentState(AllowUpdates = true)] +public WeatherForecast[]? Forecasts { get; set; } + +protected override async Task OnInitializedAsync() +{ +    Forecasts ??= await ForecastService.GetForecastAsync(); +} +``` + +To skip restoring state during prerendering, set `RestoreBehavior` to `SkipInitialValue`: + +```csharp +[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)] +public string NoPrerenderedData { get; set; } +``` + +To skip restoring state during reconnection, set `RestoreBehavior` to `SkipLastSnapshot`. This can be useful if you want to ensure fresh data after reconnection: + +```csharp +[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)] +public int CounterNotRestoredOnReconnect { get; set; } +``` + +Call `PersistentComponentState.RegisterOnRestoring` to register a callback for imperatively controlling how state is restored, similar to how provides full control of how state is persisted. From 31dcd158ac590d049dfdc3832c5d715e0693c0f8 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 8 Sep 2025 15:49:49 -0400 Subject: [PATCH 10/15] Updates --- aspnetcore/mvc/models/validation.md | 45 ----------------------------- 1 file changed, 45 deletions(-) diff --git a/aspnetcore/mvc/models/validation.md b/aspnetcore/mvc/models/validation.md index 027dcdb9d95d..c03c7eac3e0b 100644 --- a/aspnetcore/mvc/models/validation.md +++ b/aspnetcore/mvc/models/validation.md @@ -46,51 +46,6 @@ Validation attributes let you specify validation rules for model properties. The For a complete list of validation attributes, see the namespace. - - ### Error messages Validation attributes let you specify the error message to be displayed for invalid input. For example: From c7864e253cdb328ad4ec55a6dabc0a88cabaa2b2 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 8 Sep 2025 16:34:30 -0400 Subject: [PATCH 11/15] Updates --- aspnetcore/blazor/forms/validation.md | 4 ++-- aspnetcore/blazor/fundamentals/routing.md | 2 +- .../blazor/host-and-deploy/configure-trimmer.md | 14 ++++++++++++-- .../prerendered-state-persistence.md | 8 ++++---- aspnetcore/fundamentals/minimal-apis.md | 2 +- .../release-notes/aspnetcore-10/includes/blazor.md | 6 +++--- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/aspnetcore/blazor/forms/validation.md b/aspnetcore/blazor/forms/validation.md index 06f9698bb870..1381514db3a9 100644 --- a/aspnetcore/blazor/forms/validation.md +++ b/aspnetcore/blazor/forms/validation.md @@ -5,7 +5,7 @@ description: Learn how to use validation in Blazor forms. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 11/12/2024 +ms.date: 09/08/2025 uid: blazor/forms/validation --- # ASP.NET Core Blazor forms validation @@ -1664,7 +1664,7 @@ The requirement to declare the model types outside of Razor components (`.razor` ## Nested objects, collection types, and complex types > [!NOTE] -> For apps targeting .NET 10 or later, we no longer recommend the use of the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package and approach described in this section. We recommend using the built-in validation features of the the component, which received feature parity and behavioral compatibility updates with with the release of .NET 10. For more information, see . +> For apps targeting .NET 10 or later, we no longer recommend using the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package and approach described in this section. We recommend using the built-in validation features of the the component, which received feature parity and behavioral compatibility updates with at the release of .NET 10. For more information, see . Blazor provides support for validating form input using data annotations with the built-in . However, the in .NET 9 or earlier only validates top-level properties of the model bound to the form that aren't collection- or complex-type properties. diff --git a/aspnetcore/blazor/fundamentals/routing.md b/aspnetcore/blazor/fundamentals/routing.md index fd7ec4f2eeff..6a68741d66a3 100644 --- a/aspnetcore/blazor/fundamentals/routing.md +++ b/aspnetcore/blazor/fundamentals/routing.md @@ -5,7 +5,7 @@ description: Learn how to manage Blazor app request routing and how to use the N monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 11/12/2024 +ms.date: 09/08/2025 uid: blazor/fundamentals/routing --- # ASP.NET Core Blazor routing and navigation diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index a679c9b93268..f535c74a69fd 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -5,7 +5,7 @@ description: Learn how to control the Intermediate Language (IL) Trimmer when bu monikerRange: '>= aspnetcore-5.0' ms.author: wpickett ms.custom: mvc -ms.date: 09/04/2025 +ms.date: 09/08/2025 uid: blazor/host-and-deploy/configure-trimmer --- # Configure the Trimmer for ASP.NET Core Blazor @@ -125,14 +125,24 @@ The component is modified to use the `StringTuple` type: Because custom types are never trimmed by Blazor when an app is published, the component works as designed after the app is published. +:::moniker range=">= aspnetcore-10.0" + If you prefer to use framework types in spite of our recommendation, use either of the following approaches: * [Preserve the type as a dynamic dependency](#preserve-the-type-as-a-dynamic-dependency) * [Use a Root Descriptor](#use-a-root-descriptor) +:::moniker-end + +:::moniker range="< aspnetcore-10.0" + +If you prefer to use framework types in spite of our recommendation, [preserve the type as a dynamic dependency](#preserve-the-type-as-a-dynamic-dependency). + +:::moniker-end + ### Preserve the type as a dynamic dependency -We recommend creating a dynamic dependency to preserve the type with the [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute). +Create a dynamic dependency to preserve the type with the [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute). If not already present, add an `@using` directive for : diff --git a/aspnetcore/blazor/state-management/prerendered-state-persistence.md b/aspnetcore/blazor/state-management/prerendered-state-persistence.md index c6083442686a..14228127d395 100644 --- a/aspnetcore/blazor/state-management/prerendered-state-persistence.md +++ b/aspnetcore/blazor/state-management/prerendered-state-persistence.md @@ -5,7 +5,7 @@ description: Learn how to persist user data (state) in Blazor apps using Blazor' monikerRange: '>= aspnetcore-8.0' ms.author: wpickett ms.custom: mvc -ms.date: 08/05/2025 +ms.date: 09/08/2025 uid: blazor/state-management/prerendered-state-persistence --- # ASP.NET Core Blazor prerendered state persistence @@ -320,9 +320,9 @@ When the `Routes` component doesn't define a render mode, the app is using per-p Blazor supports handling persistent component state during [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling). State persisted during enhanced navigation can be read by interactive components on the page. -By default, persistent component state is only loaded by interactive components when they're initially loaded on the page. This prevents important state, such as data in an edited webform, from being overwritten if additional enhanced navigation events occur to the same page after the component is loaded. +By default, persistent component state is only loaded by interactive components when they're initially loaded on the page. This prevents important state, such as data in an edited webform, from being overwritten if additional enhanced navigation events to the same page occur after the component is loaded. -If the data is read-only and doesn't change frequently, you can opt-in to allow updates during enhanced navigation by setting `AllowUpdates = true` on the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute). This is useful for scenarios such as displaying cached data that's expensive to fetch but doesn't change often. The following example demonstrates the use of `AllowUpdates` for weather forecast data: +If the data is read-only and doesn't change frequently, opt-in to allow updates during enhanced navigation by setting `AllowUpdates = true` on the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute). This is useful for scenarios such as displaying cached data that's expensive to fetch but doesn't change often. The following example demonstrates the use of `AllowUpdates` for weather forecast data: ```csharp [PersistentState(AllowUpdates = true)] @@ -341,7 +341,7 @@ To skip restoring state during prerendering, set `RestoreBehavior` to `SkipIniti public string NoPrerenderedData { get; set; } ``` -To skip restoring state during reconnection, set `RestoreBehavior` to `SkipLastSnapshot`. This can be useful if you want to ensure fresh data after reconnection: +To skip restoring state during reconnection, set `RestoreBehavior` to `SkipLastSnapshot`. This can be useful to ensure fresh data after reconnection: ```csharp [PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)] diff --git a/aspnetcore/fundamentals/minimal-apis.md b/aspnetcore/fundamentals/minimal-apis.md index 5d407a657794..c2089186bdab 100644 --- a/aspnetcore/fundamentals/minimal-apis.md +++ b/aspnetcore/fundamentals/minimal-apis.md @@ -5,7 +5,7 @@ description: Provides an overview of Minimal APIs in ASP.NET Core ms.author: wpickett content_well_notification: AI-contribution monikerRange: '>= aspnetcore-6.0' -ms.date: 08/22/2025 +ms.date: 09/08/2025 uid: fundamentals/minimal-apis ai-usage: ai-assisted --- diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 5626be602de1..a346819f3704 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -733,9 +733,9 @@ We continue to recommend use of custom types for JS interop, JSON serialization/ Blazor now supports handling persistent component state during [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling). State persisted during enhanced navigation can be read by interactive components on the page. -By default, persistent component state is only loaded by interactive components when they're initially loaded on the page. This prevents important state, such as data in an edited webform, from being overwritten if additional enhanced navigation events occur to the same page after the component is loaded. +By default, persistent component state is only loaded by interactive components when they're initially loaded on the page. This prevents important state, such as data in an edited webform, from being overwritten if additional enhanced navigation events to the same page occur after the component is loaded. -If the data is read-only and doesn't change frequently, you can opt-in to allow updates during enhanced navigation by setting `AllowUpdates = true` on the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute). This is useful for scenarios such as displaying cached data that's expensive to fetch but doesn't change often. The following example demonstrates the use of `AllowUpdates` for weather forecast data: +If the data is read-only and doesn't change frequently, opt-in to allow updates during enhanced navigation by setting `AllowUpdates = true` on the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute). This is useful for scenarios such as displaying cached data that's expensive to fetch but doesn't change often. The following example demonstrates the use of `AllowUpdates` for weather forecast data: ```csharp [PersistentState(AllowUpdates = true)] @@ -754,7 +754,7 @@ To skip restoring state during prerendering, set `RestoreBehavior` to `SkipIniti public string NoPrerenderedData { get; set; } ``` -To skip restoring state during reconnection, set `RestoreBehavior` to `SkipLastSnapshot`. This can be useful if you want to ensure fresh data after reconnection: +To skip restoring state during reconnection, set `RestoreBehavior` to `SkipLastSnapshot`. This can be useful to ensure fresh data after reconnection: ```csharp [PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)] From 471f0aa41c633a93a8ded7df8132058d14064ac8 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:52:27 -0400 Subject: [PATCH 12/15] Remove NOTE --- aspnetcore/blazor/forms/validation.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/aspnetcore/blazor/forms/validation.md b/aspnetcore/blazor/forms/validation.md index 1381514db3a9..380a85d0d102 100644 --- a/aspnetcore/blazor/forms/validation.md +++ b/aspnetcore/blazor/forms/validation.md @@ -377,13 +377,6 @@ When validation messages are set in the component, they're added to the validato :::moniker-end -:::moniker range=">= aspnetcore-10.0" - -> [!NOTE] -> The approach in this section relies on an MVC controller for validation on the server. Although the approach is supported for all current and future versions of .NET, we plan to improve the approach by adopting [Minimal APIs](xref:fundamentals/minimal-apis) for server-side validation. The work is tracked by [Server validation with a validator component upgrade (`dotnet/AspNetCore.Docs` #36051)](https://github.com/dotnet/AspNetCore.Docs/issues/36051). - -:::moniker-end - Server validation is supported in addition to client validation: * Process client validation in the form with the component. From c1c5159002cb55a15d5b3bff0bb052875fd64758 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 9 Sep 2025 07:56:39 -0400 Subject: [PATCH 13/15] Updates for DR's feedback --- aspnetcore/blazor/fundamentals/routing.md | 16 ++--- aspnetcore/migration/90-to-100.md | 6 -- .../migration/90-to-100/includes/blazor.md | 14 ++-- aspnetcore/release-notes/aspnetcore-10.0.md | 72 ------------------- .../aspnetcore-10/includes/blazor.md | 29 ++++---- 5 files changed, 22 insertions(+), 115 deletions(-) diff --git a/aspnetcore/blazor/fundamentals/routing.md b/aspnetcore/blazor/fundamentals/routing.md index 6a68741d66a3..3aeb07577c81 100644 --- a/aspnetcore/blazor/fundamentals/routing.md +++ b/aspnetcore/blazor/fundamentals/routing.md @@ -708,28 +708,24 @@ For more information on component disposal, see relies on throwing a that gets captured by the framework, which converts the error into a redirect. Code that exists after the call to isn't called. When using Visual Studio, the debugger breaks on the exception, requiring you to deselect the checkbox for **Break when this exception type is user-handled** in the Visual Studio UI to avoid the debugger stopping for future redirects. + :::moniker-end :::moniker range=">= aspnetcore-10.0" -In .NET 9 for a redirect during static server-side rendering (static SSR), relied on throwing an exception that was captured by the framework, which converted the error into a redirect. Code that existed after the call to abruptly stopped, which also didn't match the behavior during interactive rendering. When using Visual Studio, the debugger stopped on the exception, which required deselecting the checkbox for **Break when this exception type is user-handled** in the Visual Studio UI to avoid the debugger stopping for future redirects. - -In .NET 10 or later, the logic for redirects during static SSR no longer throws an exception, so the behavior for static SSR matches the behavior for interactive render modes. Visual Studio no longer breaks on an exception. To revert to the previous behavior of throwing a in .NET 10 or later, set the following switch in the `Program` file: +You can use the `` MSBuild property set to `true` in the app's project file to opt-in to no longer throwing a . This behavior is enabled by default in the .NET 10 or later Blazor Web App project template: -```csharp -AppContext.SetSwitch( - "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", - isEnabled: false); +```xml +true ``` :::moniker-end :::moniker range=">= aspnetcore-9.0 < aspnetcore-10.0" -For a redirect during static server-side rendering (static SSR), relies on throwing an exception that gets captured by the framework, which converts the error into a redirect. Code that exists after the call to isn't called. When using Visual Studio, the debugger breaks on the exception, requiring you to deselect the checkbox for **Break when this exception type is user-handled** in the VS UI to avoid the debugger stopping for future redirects. - > [!NOTE] -> The preceding behavior was updated at the release of .NET 10 to avoid throwing an exception. To take advantage of the new behavior, upgrade the app to .NET 10 or later. +> In .NET 10 or later, you can opt-in to not throwing a by setting the `` MSBuild property to `true` in the app's project file. To take advantage of the new MSBuild property and behavior, upgrade the app to .NET 10 or later. :::moniker-end diff --git a/aspnetcore/migration/90-to-100.md b/aspnetcore/migration/90-to-100.md index 298cdf80229e..5e47dad2d25b 100644 --- a/aspnetcore/migration/90-to-100.md +++ b/aspnetcore/migration/90-to-100.md @@ -14,12 +14,6 @@ This article explains how to update an ASP.NET Core in .NET 9 to ASP.NET Core in ## Prerequisites - - # [Visual Studio](#tab/visual-studio) [!INCLUDE[](~/includes/net-prereqs-vs-10-latest.md)] diff --git a/aspnetcore/migration/90-to-100/includes/blazor.md b/aspnetcore/migration/90-to-100/includes/blazor.md index de5841944511..7056326c82f3 100644 --- a/aspnetcore/migration/90-to-100/includes/blazor.md +++ b/aspnetcore/migration/90-to-100/includes/blazor.md @@ -4,9 +4,11 @@ Complete migration coverage for Blazor apps is scheduled for September and Octob For guidance, see . -### Update the `IdentityRedirectManager` in apps based on the Blazor Web App template with Individual Accounts +### When navigation errors are disabled in a Blazor Web App with Individual Accounts -The `IdentityRedirectManager` previously threw an in the `RedirectTo` method to ensure the method wasn't called from an interactive render mode and all the redirection methods were marked with the [`[DoesNotReturn]` attribute](xref:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute). For more information, see . +*This section applies to Blazor Web Apps that set the `` MSBuild property to `true` in order to avoid throwing an navigation exception during static server-side rendering (static SSR).* + +The `IdentityRedirectManager` threw an in the `RedirectTo` method to ensure the method wasn't called from an interactive render mode and all the redirection methods were marked with the [`[DoesNotReturn]` attribute](xref:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute). The .NET 10 or later Blazor Web App project template sets the `` MSBuild property to `true` in the app's project file in order to avoid throwing the exception during static SSR. If an app based on the project template from a prior release of .NET is updated to .NET 10 or later and includes the `` MSBuild property set to `true`, make the following changes. For more information, see . In `Components/Account/IdentityRedirectManager.cs`: @@ -22,11 +24,3 @@ In `Components/Account/IdentityRedirectManager.cs`: ```diff - [DoesNotReturn] ``` - -Alternatively, to revert to the previous behavior of throwing a , set the following switch in the `Program` file: - -```csharp -AppContext.SetSwitch( - "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", - isEnabled: false); -``` diff --git a/aspnetcore/release-notes/aspnetcore-10.0.md b/aspnetcore/release-notes/aspnetcore-10.0.md index b11060f7b5e7..d1570ecc1523 100644 --- a/aspnetcore/release-notes/aspnetcore-10.0.md +++ b/aspnetcore/release-notes/aspnetcore-10.0.md @@ -130,78 +130,6 @@ if (RedirectHttpResult.IsLocalUrl(url)) Thank you [@martincostello](https://github.com/martincostello) for this contribution! -### Validation improvements for Blazor and Minimal APIs - -Several features and fixes have been added to the new validation API for Minimal APIs and Blazor, introducing feature parity and behavioral compatibility with the existing . - -#### Type-level validation attributes - -Validation now supports attributes placed on classes and records. - -Consider the following attribute to validate a sum limit: - -```csharp -class SumLimitAttribute(int Limit) : ValidationAttribute -{ - protected override ValidationResult? IsValid(object? value, ValidationContext _) - { - if (value is Point point && point.X + point.Y > Limit) - { - return new ValidationResult("The sum of X and Y is too high"); - } - - return ValidationResult.Success; - } -} -``` - -The attribute can now be placed on a record: - -```csharp -[SumLimit(42)] -record Point(int X, int Y); -``` - -#### Skip validation - -Use the new `[SkipValidation]` attribute to omit selected properties, parameters, or types from validation. When applied to a property or a method parameter, the validator skips that value during validation. When applied to a type, the validator skips all properties and parameters of that type. - -This can be useful, in particular, when using the same model types in cases that require and don't require validation. - -In the following example, validation is skipped for `ContactAddress` by applying the `[SkipValidation]` attribute to its property in the `Order` class, in spite of `Address.Street` normally requiring a value: - -```csharp -class Address -{ - [Required] - public string Street { get; set; } - - // ... -} - -class Order -{ - public Address PaymentAddress { get; set; } - - [SkipValidation] - public Address ContactAddress { get; set; } - - // ... -} -``` - -Additionally, properties annotated with the [`[JsonIgnore]` attribute](xref:System.Text.Json.Serialization.JsonIgnoreAttribute) are now also omitted from validation to improve consistency between serialization and validation in the context of JSON models. Note that the `[SkipValidation]` attribute should be preferred in general cases. - -#### Backwards-compatible behavior - -Type validation logic has been updated to match the validation order and short-circuiting behavior of . This means that the following rules are applied when validating an instance of type `T`: - -1. Member properties of `T` are validated, including recursively validating nested objects. -1. Type-level attributes of `T` are validated. -1. The method is executed, if `T` implements it. - -If one of the preceding steps produces a validation error, the remaining steps are skipped. - ## Breaking changes Use the articles in [Breaking changes in .NET](/dotnet/core/compatibility/breaking-changes) to find breaking changes that might apply when upgrading an app to a newer version of .NET. diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index a346819f3704..2e84f2f4f5f4 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -372,21 +372,23 @@ In Blazor Web Apps, framework static assets are automatically preloaded using [` For more information, see . -### `NavigationManager.NavigateTo` no longer throws a `NavigationException` +### Opt-in to avoiding a `NavigationException` during static server-side rendering with `NavigationManager.NavigateTo` -Previously, calling during static server-side rendering (static SSR) would throw a , interrupting execution before being converted to a redirection response. This caused confusion during debugging and was inconsistent with interactive rendering, where code after continues to execute normally. +Calling during static server-side rendering (static SSR) throws a , interrupting execution before being converted to a redirection response. This can cause confusion during debugging and is inconsistent with interactive rendering behavior, where code after continues to execute normally. -Calling during static SSR no longer throws a . Instead, it behaves consistently with interactive rendering by performing the navigation without throwing an exception. +In .NET 10, you can set the `` MSBuild property to `true` in the app's project file in order to avoid throwing the exception during static SSR: -Code that relied on being thrown should be updated. For example, in the default Blazor Identity UI, the `IdentityRedirectManager` previously threw an after calling `RedirectTo` to ensure it wasn't invoked during interactive rendering. This exception and the [`[DoesNotReturn]` attributes](xref:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute) should now be removed. +```xml + + true + +``` -To revert to the previous behavior of throwing a , set the following switch in the `Program` file: +With the MSBuild property set, calling during static SSR no longer throws a . Instead, it behaves consistently with interactive rendering by performing the navigation without throwing an exception. Code after executes before the redirection occurs. -```csharp -AppContext.SetSwitch( - "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", - isEnabled: false); -``` +The .NET 10 Blazor Web App project template sets the MSBuild property to `true` by default. We recommend that apps updating to .NET 10 use the new MSBuild property and avoid the prior behavior. + +If the MSBuild property is used, code that relied on being thrown should be updated. In the default Blazor Identity UI of the Blazor Web App project template before the release of .NET 10, the `IdentityRedirectManager` throws an after calling `RedirectTo` to ensure that the method wasn't invoked during interactive rendering. This exception and the [`[DoesNotReturn]` attributes](xref:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute) should now be removed when the MSBuild property is used. For more information, see . ### Blazor router has a `NotFoundPage` parameter @@ -722,13 +724,6 @@ In the following example, a hidden input field is created for the form's `Parame } ``` -### Preservation of types used by a published app - -We continue to recommend use of custom types for JS interop, JSON serialization/deserialization, and other operations that rely on [reflection](/dotnet/csharp/advanced-topics/reflection-and-attributes/). However, using framework types that ordinarily are trimmed when publishing an app remains supported with the following approaches: - -* [Preserve the type as a dynamic dependency](xref:blazor/host-and-deploy/configure-trimmer#preserve-the-type-as-a-dynamic-dependency): Supported since .NET 5. -* [Use a Root Descriptor](xref:blazor/host-and-deploy/configure-trimmer#use-a-root-descriptor): *A new approach for apps targeting .NET 10 or later.* - ### Persistent component state support for enhanced navigation Blazor now supports handling persistent component state during [enhanced navigation](xref:blazor/fundamentals/routing#enhanced-navigation-and-form-handling). State persisted during enhanced navigation can be read by interactive components on the page. From 37f7f70656368fded00612140ec2a986a33800ae Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:16:47 -0400 Subject: [PATCH 14/15] Updates --- aspnetcore/blazor/forms/validation.md | 24 ++++++++++++------- .../aspnetcore-10/includes/blazor.md | 6 +++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/aspnetcore/blazor/forms/validation.md b/aspnetcore/blazor/forms/validation.md index 380a85d0d102..640c178a175a 100644 --- a/aspnetcore/blazor/forms/validation.md +++ b/aspnetcore/blazor/forms/validation.md @@ -136,13 +136,7 @@ The compon :::moniker range=">= aspnetcore-10.0" -The component has the same validation order and short-circuiting behavior as . The following rules are applied when validating an instance of type `T`: - -1. Member properties of `T` are validated, including recursively validating nested objects. -1. Type-level attributes of `T` are validated. -1. The method is executed, if `T` implements it. - -If one of the preceding steps produces a validation error, the remaining steps are skipped. +For details on validation behavior, see the [`DataAnnotationsValidator` validation behavior](#dataannotationsvalidator-validation-behavior) section. :::moniker-end @@ -1657,7 +1651,7 @@ The requirement to declare the model types outside of Razor components (`.razor` ## Nested objects, collection types, and complex types > [!NOTE] -> For apps targeting .NET 10 or later, we no longer recommend using the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package and approach described in this section. We recommend using the built-in validation features of the the component, which received feature parity and behavioral compatibility updates with at the release of .NET 10. For more information, see . +> For apps targeting .NET 10 or later, we no longer recommend using the *experimental* [`Microsoft.AspNetCore.Components.DataAnnotations.Validation`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.DataAnnotations.Validation) package and approach described in this section. We recommend using the built-in validation features of the the component. Blazor provides support for validating form input using data annotations with the built-in . However, the in .NET 9 or earlier only validates top-level properties of the model bound to the form that aren't collection- or complex-type properties. @@ -1835,3 +1829,17 @@ A side effect of the preceding approach is that a validation summary ( component has the same validation order and short-circuiting behavior as . The following rules are applied when validating an instance of type `T`: + +1. Member properties of `T` are validated, including recursively validating nested objects. +1. Type-level attributes of `T` are validated. +1. The method is executed, if `T` implements it. + +If one of the preceding steps produces a validation error, the remaining steps are skipped. + +:::moniker-end diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 2e84f2f4f5f4..5ca68d40137d 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -591,8 +591,10 @@ The requirement to declare the model types outside of Razor components (`.razor` Validation support now includes: -* Placing validation attributes on classes and records. -* The new `[SkipValidation]` attribute to omit selected properties, parameters, or types from validation. When applied to a property or a method parameter, the validator skips that value during validation. When applied to a type, the validator skips all properties and parameters of that type. This can be useful, in particular, when using the same model types in cases that require and don't require validation. +* Validation of nested complex objects and collections is now supported. + * This includes validation rules defined by property attributes, class attributes, and the implementation. + * The new `[SkipValidation]` attribute was added to solve a problem that previously didn't exist (nested objects weren't validated in any case). +* Validation now uses a source generator-based implementation instead of reflection-based implementation for improved performance and compatibility with ahead-of-time (AOT) compilation. The component now has the same validation order and short-circuiting behavior as . The following rules are applied when validating an instance of type `T`: From cc9160febe275577e714ad0e6ba89147f21d746e Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:05:09 -0400 Subject: [PATCH 15/15] Updates --- aspnetcore/release-notes/aspnetcore-10/includes/blazor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 5ca68d40137d..57d917eb7d4d 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -593,7 +593,7 @@ Validation support now includes: * Validation of nested complex objects and collections is now supported. * This includes validation rules defined by property attributes, class attributes, and the implementation. - * The new `[SkipValidation]` attribute was added to solve a problem that previously didn't exist (nested objects weren't validated in any case). + * The `[SkipValidation]` attribute can exclude properties or types from validation. * Validation now uses a source generator-based implementation instead of reflection-based implementation for improved performance and compatibility with ahead-of-time (AOT) compilation. The component now has the same validation order and short-circuiting behavior as . The following rules are applied when validating an instance of type `T`: