注意
此版本不是本文的最新版本。 对于当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 NET Core 支持策略。 对于当前版本,请参阅本文的 .NET 9 版本。
本文介绍在构建 Blazor 应用时如何控制中间语言 (IL) 裁边器。
Blazor WebAssembly 执行中间语言 (IL) 裁剪以减小发布输出的大小。 裁剪操作在发布应用程序时进行。
默认裁剪器粒度
应用 Blazor 的默认修剪粒度是 partial,这意味着只有核心框架库和那些已显式启用修剪支持的库会被修剪。 不支持完整裁剪。
有关详细信息,请参阅《裁剪选项》(.NET 文档)。
配置
若要配置 IL 裁剪器,请参阅 .NET 基础知识文档中的裁剪选项一文,其中包括有关以下主题的指导:
- 通过项目文件中的
<PublishTrimmed>属性对整个应用禁用裁剪。 - 控制 IL 裁剪器如何放弃未充分利用的 IL。
- 阻止 IL 裁剪器裁剪特定程序集。
- 要裁剪的“根”程序集。
- 通过在项目文件中将
<SuppressTrimAnalysisWarnings>属性设置为false来显示关于反射的类型的警告。 - 控制符号裁剪和调试程序支持。
- 设置 IL 裁剪器功能以裁剪框架库功能。
当 剪裁器粒度 为 partial默认值时,IL 修整程序会剪裁基类库和标记为可修整的任何其他程序集。 若要在应用程序的任何类库项目中启用剪裁功能,请在这些项目中将 MSBuild 属性设置为<IsTrimmable>true。
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
与 .NET 库相关的指南,请参阅针对精简的 .NET 库的准备。
未能保留已发布应用程序所使用的类型
即使将属性设置为<PublishTrimmed>false项目文件中,修整也可能对已发布的应用产生不利影响,从而导致运行时错误。 在使用反射的应用程序中,IL 裁剪器通常无法确定运行时反射所需的类型,进而会将这些类型裁剪掉,或者从方法中裁剪掉参数名称。 对于用于 JS 互操作、JSON 序列化/反序列化以及其他操作的复杂框架类型,可能出现这种情况。
IL 裁剪器也无法在运行时对应用的动态行为作出响应。 若要确保裁剪后的应用在部署后正常工作,请在开发时经常对已发布的输出进行测试。
请考虑以下示例,该示例将 JSON 反序列化执行到 Tuple<T1,T2> 集合中(List<Tuple<string, string>>)。
TrimExample.razor:
@page "/trim-example"
@using System.Diagnostics.CodeAnalysis
@using System.Text.Json
<h1>Trim Example</h1>
<ul>
@foreach (var item in @items)
{
<li>@item.Item1, @item.Item2</li>
}
</ul>
@code {
private List<Tuple<string, string>> 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<List<Tuple<string, string>>>(data, options)!;
}
}
当应用在本地运行并生成以下呈现的列表时,上述组件将正常执行:
• 1:T1、1:T2
• 2:T2、2:T2
发布应用时,Tuple<T1,T2>即使将属性设置为<PublishTrimmed>false项目文件中,也会从应用剪裁。 访问该组件会引发以下异常:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: ConstructorContainsNullParameterNames, System.Tuple`2[System.String,System.String]
System.NotSupportedException: ConstructorContainsNullParameterNames, System.Tuple`2[System.String,System.String]
若要解决丢失的类型,请考虑采用以下方法之一。
自定义类型
为了避免在依赖于反射(如 JS 互操作和 JSON 序列化)的场景中 .NET 出现剪裁问题,请使用在不可裁剪的库中定义的自定义类型,或通过链接器配置保留这些类型。
以下修改会创建一个 StringTuple 类型供该组件使用。
StringTuple.cs:
[method: SetsRequiredMembers]
public sealed class StringTuple(string item1, string item2)
{
public required string Item1 { get; init; } = item1;
public required string Item2 { get; init; } = item2;
}
对该组件进行修改,使其使用 StringTuple 类型:
- private List<Tuple<string, string>> items = [];
+ private List<StringTuple> items = [];
- items = JsonSerializer.Deserialize<List<Tuple<string, string>>>(data, options)!;
+ items = JsonSerializer.Deserialize<List<StringTuple>>(data, options)!;
由于自定义类型在不可修剪的程序集内定义,并且发布应用程序时不会被 Blazor 修剪,因此在发布应用程序后,组件能够按照设计正常运行。
如果你更喜欢使用框架类型,尽管我们的建议,请使用以下任一方法:
如果你希望尽管建议使用框架类型, 请保留该类型作为动态依赖项。
将类型保留为动态依赖项
创建动态依赖项以保留具有特性的类型[DynamicDependency]。
如果 @using 指令尚不存在,请添加该指令用于 System.Diagnostics.CodeAnalysis:
@using System.Diagnostics.CodeAnalysis
添加 [DynamicDependency] 属性以保留 Tuple<T1,T2>:
+ [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors,
+ typeof(Tuple<string, string>))]
private List<Tuple<string, string>> items = [];
使用根描述符
根描述符可以保留类型。
将 ILLink.Descriptors.xml 文件添加到应用的根目录†类型:
<linker>
<assembly fullname="System.Private.CoreLib">
<type fullname="System.Tuple`2" preserve="all" />
</assembly>
</linker>
†应用的根是指应用的根或 (.NET 8 或更高版本)项目的Blazor WebAssembly根.ClientBlazor Web App目录。
将项 TrimmerRootDescriptor 添加到应用的项目文件? 引用 ILLink.Descriptors.xml 该文件:
<ItemGroup>
<TrimmerRootDescriptor Include="$(MSBuildThisFileDirectory)ILLink.Descriptors.xml" />
</ItemGroup>
•项目文件是应用的项目文件Blazor WebAssembly,或者是项目(.NET 8 或更高版本)的项目.Client文件Blazor Web App。
.NET 8 中的解决方法
作为 .NET 8 中的一种解决方法,可以将 _ExtraTrimmerArgs MSBuild 属性添加到 --keep-metadata parametername 应用的项目文件中,以在修整期间保留参数名称:
<PropertyGroup>
<_ExtraTrimmerArgs>--keep-metadata parametername</_ExtraTrimmerArgs>
</PropertyGroup>