-
Notifications
You must be signed in to change notification settings - Fork 25.1k
Blazor RC1 coverage #36053
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blazor RC1 coverage #36053
Changes from all commits
b6eec36
ab14017
54af8d0
42419ce
76a6568
5c7cc79
c738aba
5705ecf
62e97f2
31dcd15
c7864e2
471f0aa
c1c5159
37f7f70
cc9160f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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/08/2025 | ||
uid: blazor/host-and-deploy/configure-trimmer | ||
--- | ||
# Configure the Trimmer for ASP.NET Core Blazor | ||
|
@@ -42,73 +42,123 @@ For more information, see [Trimming options (.NET documentation)](/dotnet/core/d | |
|
||
## Failure to preserve types used by a published app | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @javiercn Can you review the recommendations made in this section for dealing with type preservation for JS interop related scenarios? |
||
|
||
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 [`<PublishTrimmed>` 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This doesn't sound right. Setting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll double check this in the morning and report back. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gaurdrex Can you create an issue for this in the dotnet/aspnetcore repo and share your project with us? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @danroth27 ... Isn't this the same as (or at least has the elements of) dotnet/aspnetcore#52947? It's just not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... and @danroth27 ... are we holding this PR past the RC1 release if @javiercn doesn't respond by then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right, that's why I thought @javiercn might want to review this content.
Nope, no need to block on this. The content looks fine to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just asked because you were calling for his review. I'll react with a patch PR later if he has any changes. ... and I thought Javier would just re-open the existing PU issue on that subject, but I'm still 👂 if you/he want a new issue on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We did KeyValuePair because people use that. If get reports for other types, we might choose to add those too. The cost of preserving a type is small compared to the benefit of not having to force people into doing this type of stuff if enough people use the type. |
||
|
||
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 <xref:System.Collections.Generic.KeyValuePair> collection (`List<KeyValuePair<string, string>>`): | ||
Consider the following example that performs JSON deserialization into a <xref:System.Tuple%602> collection (`List<Tuple<string, string>>`). | ||
|
||
`TrimExample.razor`: | ||
|
||
```razor | ||
@rendermode @(new InteractiveWebAssemblyRenderMode(false)) | ||
@page "/trim-example" | ||
@using System.Diagnostics.CodeAnalysis | ||
@using System.Text.Json | ||
|
||
<dl> | ||
<h1>Trim Example</h1> | ||
|
||
<ul> | ||
@foreach (var item in @items) | ||
{ | ||
<dt>@item.Key</dt> | ||
<dd>@item.Value</dd> | ||
<li>@item.Item1, @item.Item2</li> | ||
} | ||
</dl> | ||
</ul> | ||
|
||
@code { | ||
private List<KeyValuePair<string, string>> items = []; | ||
private List<Tuple<string, string>> items = []; | ||
|
||
[StringSyntax(StringSyntaxAttribute.Json)] | ||
private const string data = | ||
"""[{"key":"key 1","value":"value 1"},{"key":"key 2","value":"value 2"}]"""; | ||
"""[{"item1":"1:T1","item2":"1:T2"},{"item1":"2:T1","item2":"2:T2"}]"""; | ||
|
||
protected override void OnInitialized() | ||
{ | ||
JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; | ||
|
||
items = JsonSerializer | ||
.Deserialize<List<KeyValuePair<string, string>>>(data, options)!; | ||
.Deserialize<List<Tuple<string, string>>>(data, options)!; | ||
} | ||
} | ||
``` | ||
|
||
The preceding component executes normally when the app is run locally and produces the following rendered definition list (`<dl>`): | ||
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, <xref:System.Tuple%602> is trimmed from the app, even in spite of setting the `<PublishTrimmed>` 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 `StringTuple` 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 `StringTuple` type: | ||
|
||
```diff | ||
- private List<Tuple<string, string>> items = []; | ||
+ private List<StringTuple> items = []; | ||
``` | ||
|
||
```diff | ||
- items = JsonSerializer.Deserialize<List<Tuple<string, string>>>(data, options)!; | ||
+ items = JsonSerializer.Deserialize<List<StringTuple>>(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. | ||
|
||
:::moniker range=">= aspnetcore-10.0" | ||
|
||
> **:::no-loc text="key 1":::** | ||
> :::no-loc text="value 1"::: | ||
> **:::no-loc text="key 2":::** | ||
> :::no-loc text="value 2"::: | ||
If you prefer to use framework types in spite of our recommendation, use either of the following approaches: | ||
|
||
When the app is published, <xref:System.Collections.Generic.KeyValuePair> is trimmed from the app, even in spite of setting the [`<PublishTrimmed>` property](#configuration) to `false` in the project file. Accessing the component throws the following exception: | ||
* [Preserve the type as a dynamic dependency](#preserve-the-type-as-a-dynamic-dependency) | ||
* [Use a Root Descriptor](#use-a-root-descriptor) | ||
|
||
> :::no-loc text="Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Collections.Generic.KeyValuePair`2[System.String,System.String]"::: | ||
:::moniker-end | ||
|
||
To address lost types, consider the following approaches. | ||
:::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 <xref:System.Diagnostics.CodeAnalysis?displayProperty=fullName>: | ||
|
||
```razor | ||
@using System.Diagnostics.CodeAnalysis | ||
``` | ||
|
||
Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute) to preserve the <xref:System.Collections.Generic.KeyValuePair>: | ||
Add a [`[DynamicDependency]` attribute](xref:System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute) to preserve the <xref:System.Tuple%602>: | ||
|
||
```diff | ||
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, typeof(KeyValuePair<string, string>))] | ||
private List<KeyValuePair<string, string>> items = []; | ||
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, | ||
+ typeof(Tuple<string, string>))] | ||
private List<Tuple<string, string>> items = []; | ||
``` | ||
|
||
<!-- UPDATE 10.0 - Hold this for https://github.com/dotnet/aspnetcore/issues/52947 | ||
:::moniker range=">= aspnetcore-10.0" | ||
|
||
### Use a Root Descriptor | ||
|
||
|
@@ -118,10 +168,8 @@ Add an `ILLink.Descriptors.xml` file to the root of the app† with the typ | |
|
||
```xml | ||
<linker> | ||
<assembly fullname="System.Runtime"> | ||
<type fullname="System.Collections.Generic.KeyValuePair`2"> | ||
<method signature="System.Void .ctor(TKey,TValue)" /> | ||
</type> | ||
<assembly fullname="System.Private.CoreLib"> | ||
<type fullname="System.Tuple`2" preserve="all" /> | ||
</assembly> | ||
</linker> | ||
``` | ||
|
@@ -138,42 +186,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). | ||
|
||
--> | ||
|
||
### Custom types | ||
|
||
<!-- UPDATE 10.0 - We'll hold this for when the file descriptor approach comes back. | ||
|
||
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. | ||
|
||
--> | ||
|
||
The following modifications create a `StringKeyValuePair` type for use by the component. | ||
|
||
`StringKeyValuePair.cs`: | ||
:::moniker-end | ||
|
||
```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; | ||
} | ||
``` | ||
:::moniker range="= aspnetcore-8.0" | ||
|
||
The component is modified to use the `StringKeyValuePair` type: | ||
### Workaround in .NET 8 | ||
|
||
```diff | ||
- private List<KeyValuePair<string, string>> items = []; | ||
+ private List<StringKeyValuePair> items = []; | ||
``` | ||
As a workaround in .NET 8, you can add the `_ExtraTrimmerArgs` MSBuild property set to `--keep-metadata parametername` in the app's project file to preserve parameter names during trimming: | ||
|
||
```diff | ||
- items = JsonSerializer.Deserialize<List<KeyValuePair<string, string>>>(data, options)!; | ||
+ items = JsonSerializer.Deserialize<List<StringKeyValuePair>>(data, options)!; | ||
```xml | ||
<PropertyGroup> | ||
<_ExtraTrimmerArgs>--keep-metadata parametername</_ExtraTrimmerArgs> | ||
</PropertyGroup> | ||
``` | ||
|
||
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 | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.