From 01a60905013b50a34588753db0e9e42d075db91a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:45:00 -0500 Subject: [PATCH 1/9] Failure to preserve types enhancements --- .../host-and-deploy/configure-trimmer.md | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 98018ec7aee8..e363e2888d55 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -16,8 +16,6 @@ This article explains how to control the Intermediate Language (IL) Trimmer when Blazor WebAssembly performs [Intermediate Language (IL)](/dotnet/standard/glossary#il) trimming to reduce the size of the published output. Trimming occurs when publishing an app. -Trimming may have detrimental effects for the published app. 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 trim them away. For example, complex framework types for JS interop, such as , might be trimmed and not available at runtime for JS interop calls. In these cases, we recommend creating your own custom types instead. 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. - ## Configuration To configure the IL Trimmer, see the [Trimming options](/dotnet/core/deploying/trimming/trimming-options) article in the .NET Fundamentals documentation, which includes guidance on the following subjects: @@ -42,6 +40,89 @@ The default trimmer granularity for Blazor apps is `partial`. To trim all assemb For more information, see [Trimming options (.NET documentation)](/dotnet/core/deploying/trimming/trimming-options#trimming-granularity). +## Failure to preserve types used by a published app + +Trimming may have detrimental effects for a published app. 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. This can happen for types used for JS interop and JSON serialization/deserialization. For example, complex framework types, such as , might be trimmed and not available at runtime. + +Consider the following client-side component in a Blazor Web App (ASP.NET Core 8.0 or later) that deserializes a collection (`List>`): + +```razor +@rendermode @(new InteractiveWebAssemblyRenderMode(false)) +@using System.Text.Json + +
+ @foreach (var item in @items) + { +
@item.Key
+
@item.Value
+ } +
+ +@code { + private List> items = []; + + protected override void OnInitialized() + { + var data = "[ { \"key\" : \"key 1\", \"value\" : \"value 1\" }, " + + "{ \"key\" : \"key 2\", \"value\" : \"value 2\" } ]"; + + JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; + + items = JsonSerializer + .Deserialize>>(data, options)!; + } +} +``` + +The preceding component executes normally when the app is run locally: + +> **:::no-loc text="key 1":::** +> :::no-loc text="value 1"::: +> **:::no-loc text="key 2":::** +> :::no-loc text="value 2"::: + +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: + +> :::no-loc text="Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String]"::: + +In these cases, we recommend creating a custom type. 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. Because custom types are never trimmed by Blazor when an app is published, the component works as designed: + +```razor +@using System.Diagnostics.CodeAnalysis + +... + +@code { + private List items = []; + + protected override void OnInitialized() + { + var data = "[ { \"key\" : \"key 1\", \"value\" : \"value 1\" }, " + + "{ \"key\" : \"key 2\", \"value\" : \"value 2\" } ]"; + + JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; + + items = JsonSerializer + .Deserialize>(data, options)!; + } +} +``` + +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. + ## Additional resources * [Trim self-contained deployments and executables](/dotnet/core/deploying/trimming/trim-self-contained) From ec4b97c3b5e395ad11d90707a1650e4f8ced54fc Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:47:06 -0500 Subject: [PATCH 2/9] 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 284a7141e461..d5693d86ee69 100644 --- a/aspnetcore/blazor/javascript-interoperability/index.md +++ b/aspnetcore/blazor/javascript-interoperability/index.md @@ -201,7 +201,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) and not present for JS interop. We recommend creating custom types for types that the IL Trimmer trims away. +* 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. * 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 b3db5954e6e0535909c94fe4f8b5ccd11bc170de Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 14 Feb 2025 07:35:07 -0500 Subject: [PATCH 3/9] Updates --- .../host-and-deploy/configure-trimmer.md | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index e363e2888d55..974b586105ad 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -42,12 +42,13 @@ 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. 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. This can happen for types used for JS interop and JSON serialization/deserialization. For example, complex framework types, such as , might be trimmed and not available at runtime. +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. This can happen with complex framework types used for JS interop, JSON serialization/deserialization, and other operations. Consider the following client-side component in a Blazor Web App (ASP.NET Core 8.0 or later) that deserializes a collection (`List>`): ```razor @rendermode @(new InteractiveWebAssemblyRenderMode(false)) +@using System.Diagnostics.CodeAnalysis @using System.Text.Json
@@ -61,11 +62,12 @@ Consider the following client-side component in a Blazor Web App (ASP.NET Core 8 @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() { - var data = "[ { \"key\" : \"key 1\", \"value\" : \"value 1\" }, " + - "{ \"key\" : \"key 2\", \"value\" : \"value 2\" } ]"; - JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; items = JsonSerializer @@ -101,18 +103,15 @@ public sealed class StringKeyValuePair(string key, string value) The component is modified to use the `StringKeyValuePair` type. Because custom types are never trimmed by Blazor when an app is published, the component works as designed: ```razor -@using System.Diagnostics.CodeAnalysis - -... - @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() { - var data = "[ { \"key\" : \"key 1\", \"value\" : \"value 1\" }, " + - "{ \"key\" : \"key 2\", \"value\" : \"value 2\" } ]"; - JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; items = JsonSerializer From db73e208e1795c7c702f93fe65d23a68b2dd9a26 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 14 Feb 2025 08:21:35 -0500 Subject: [PATCH 4/9] Updates --- .../host-and-deploy/configure-trimmer.md | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 974b586105ad..8535afd84e02 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -42,7 +42,7 @@ 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. 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. 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. Consider the following client-side component in a Blazor Web App (ASP.NET Core 8.0 or later) that deserializes a collection (`List>`): @@ -76,7 +76,7 @@ Consider the following client-side component in a Blazor Web App (ASP.NET Core 8 } ``` -The preceding component executes normally when the app is run locally: +The preceding component executes normally when the app is run locally and produces the following rendered definition list (`
`): > **:::no-loc text="key 1":::** > :::no-loc text="value 1"::: @@ -100,26 +100,20 @@ public sealed class StringKeyValuePair(string key, string value) } ``` -The component is modified to use the `StringKeyValuePair` type. Because custom types are never trimmed by Blazor when an app is published, the component works as designed: +The component is modified to use the `StringKeyValuePair` type: -```razor -@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 }; +```diff +- private List> items = []; ++ private List items = []; +``` - items = JsonSerializer - .Deserialize>(data, options)!; - } -} +```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. + 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. ## Additional resources From 05e9a450182301773a7a80dabc364b1b0efec82e Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:36:38 -0500 Subject: [PATCH 5/9] Updates --- .../host-and-deploy/configure-trimmer.md | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 8535afd84e02..5e471810ed20 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -44,6 +44,8 @@ For more information, see [Trimming options (.NET documentation)](/dotnet/core/d 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. +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 (ASP.NET Core 8.0 or later) that deserializes a collection (`List>`): ```razor @@ -87,7 +89,60 @@ When the app is published, is tri > :::no-loc text="Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String]"::: -In these cases, we recommend creating a custom type. The following modifications create a `StringKeyValuePair` type for use by the component. + + +To address lost types, we recommend taking **either** of the following approaches. + +### Preserve the type as a dynamic dependency + +If not already present, add an `@using` directive for : + +```razor +@using System.Diagnostics.CodeAnalysis +``` + +Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute) to preserve the : + +```diff ++ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(KeyValuePair))] +private List> items = []; +``` + + + +### Create a custom type + +The following modifications create a `StringKeyValuePair` type for use by the component. `StringKeyValuePair.cs`: @@ -114,8 +169,6 @@ The component is modified to use the `StringKeyValuePair` type: Because custom types are never trimmed by Blazor when an app is published, the component works as designed after the app is published. -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. - ## Additional resources * [Trim self-contained deployments and executables](/dotnet/core/deploying/trimming/trim-self-contained) From 30160b1cf8797401999afb07020433918b29ad2c Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:22:33 -0500 Subject: [PATCH 6/9] Updates --- .../host-and-deploy/configure-trimmer.md | 53 ++++--------------- 1 file changed, 11 insertions(+), 42 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index 5e471810ed20..c8432eb0ff2b 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -95,6 +95,8 @@ To address lost types, we recommend taking **either** of the following approache ### 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). + If not already present, add an `@using` directive for : ```razor @@ -108,66 +110,33 @@ Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.Dyn private List> items = []; ``` - - -### Create a custom type - -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)!; -``` +### Custom types -Because custom types are never trimmed by Blazor when an app is published, the component works as designed after the app is published. +Custom types aren't trimmed by Blazor when an app is published, but we recommend [preserving types as dynamic dependencies](#preserve-the-type-as-a-dynamic-dependency) instead of creating custom types. ## Additional resources From 9be32ca3f81568dea0215332c2e3871f5efb0cfa Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:28:21 -0500 Subject: [PATCH 7/9] Updates --- .../blazor/host-and-deploy/configure-trimmer.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index c8432eb0ff2b..d6f796c35d84 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -114,19 +114,21 @@ private List> items = []; A [Root Descriptor](/dotnet/core/deploying/trimming/trimming-options#root-descriptors) can preserve the type. -Add a `ILLink.Descriptors.xml` file to the root of the app with the type: +Add an `ILLink.Descriptors.xml` file to the root of the app† with the type: ```xml - - + + ``` -Add a `TrimmerRootDescriptor` item to the server app's project file referencing the `ILLink.Descriptors.xml` file: +†The root of the app refers to the root of the Blazor WebAssembly app or the root of the `.Client` project of a Blazor Web App (.NET 8 or later). + +Add a `TrimmerRootDescriptor` item to the app's project file‡ referencing the `ILLink.Descriptors.xml` file: ```xml @@ -134,6 +136,8 @@ Add a `TrimmerRootDescriptor` item to the server 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). + ### Custom types Custom types aren't trimmed by Blazor when an app is published, but we recommend [preserving types as dynamic dependencies](#preserve-the-type-as-a-dynamic-dependency) instead of creating custom types. From 024ff063de57c04969dac383ca67a4a73e9d6b6e Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:01:57 -0500 Subject: [PATCH 8/9] Updates --- .../host-and-deploy/configure-trimmer.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index d6f796c35d84..dde06c51ab6c 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -110,6 +110,8 @@ Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.Dyn private List> items = []; ``` + + ### Custom types + + +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)!; +``` + +Because custom types are never trimmed by Blazor when an app is published, the component works as designed after the app is published. + ## Additional resources * [Trim self-contained deployments and executables](/dotnet/core/deploying/trimming/trim-self-contained) From fa5b76550f4f933579ac2113ad04d2e72d01b16b Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:09:01 -0500 Subject: [PATCH 9/9] Updates --- aspnetcore/blazor/host-and-deploy/configure-trimmer.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md index dde06c51ab6c..5d7df3b0f1ae 100644 --- a/aspnetcore/blazor/host-and-deploy/configure-trimmer.md +++ b/aspnetcore/blazor/host-and-deploy/configure-trimmer.md @@ -89,9 +89,7 @@ When the app is published, is tri > :::no-loc text="Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String]"::: - - -To address lost types, we recommend taking **either** of the following approaches. +To address lost types, consider the following approaches. ### Preserve the type as a dynamic dependency