指令
:更改组件标记的分析或运行方式。 例如,
@page
指令使用路由模板指定可路由组件,可以由用户请求在浏览器中按特定 URL 直接访问。
指令特性
:更改组件元素的分析方式或运行方式。 例如,
<input>
元素的
@bind
指令特性会将数据绑定到元素的值。
本文和 Blazor 文档集的其他文章中进一步说明了在组件中使用的指令和指令特性。 有关 Razor 语法的一般信息,请参阅
ASP.NET Core 的 Razor 语法参考
。
组件名称、类名和命名空间
组件的名称必须以大写字符开头:
✔️
支持:
ProductDetail.razor
❌
不支持:
productDetail.razor
整个 Blazor 文档中使用的常见 Blazor 命名约定包括:
文件路径和文件名使用 †Pascal 大小写,且在显示组件代码示例之前出现。 如果路径存在,则表示典型的文件夹位置。 例如,
Components/Pages/ProductDetail.razor
表示
ProductDetail
组件具有文件名
ProductDetail.razor
,并位于应用的
Components
文件夹的
Pages
文件夹中。
可路由组件的组件文件路径与其 ‡kebab 大小写形式的 URL 匹配,组件路由模板中各单词之间会显示连字符。 例如,在浏览器中,通过相对 URL
/product-detail
请求具有路由模板
/product-detail
(
@page "/product-detail"
) 的
ProductDetail
组件。
†Pascal 大小写(大写 camel 形式)是不带空格和标点符号的命名约定,其中每个单词的首字母大写(包括第一个单词)。
‡Kebab 大小写是一种命名约定,不使用空格和标点符号,它使用小写字母,且单词之间有短划线。
组件是普通
C# 类
,可以放置在项目中的任何位置。 生成网页的组件通常位于
Components/Pages
文件夹中。 非页面组件通常放置在
Components
文件夹或添加到项目的自定义文件夹中。
通常,组件的命名空间是从应用的根命名空间和该组件在应用内的位置(文件夹)派生而来的。 如果应用的根命名空间是
BlazorSample
,并且
Counter
组件位于
Components/Pages
文件夹中:
Counter
组件的命名空间为
BlazorSample.Components.Pages
。
组件的完全限定类型名称为
BlazorSample.Components.Pages.Counter
。
对于保存组件的自定义文件夹,将
@using
指令添加到父组件或应用的
_Imports.razor
文件。 下面的示例提供
AdminComponents
文件夹中的组件:
@using BlazorSample.AdminComponents
_Imports.razor
文件中的 @using
指令仅适用于 Razor 文件 (.razor
),而不适用于 C# 文件 (.cs
)。
不支持使用别名的 using
语句。 在以下示例中,GridRendering
组件的公共 WeatherForecast
类作为应用其他位置的组件中的 WeatherForecast
提供:
@using WeatherForecast = Components.Pages.GridRendering.WeatherForecast
还可以使用其完全限定的名称来引用组件,这时不需要 @using
指令。 以下示例直接引用应用的 AdminComponents/Pages
文件夹中的 ProductDetail
组件:
<BlazorSample.AdminComponents.Pages.ProductDetail />
使用 Razor 创建的组件的命名空间基于以下内容(按优先级顺序):
Razor 文件标记中的 @namespace
指令(例如 @namespace BlazorSample.CustomNamespace
)。
项目文件中项目的 RootNamespace
(例如 <RootNamespace>BlazorSample</RootNamespace>
)。
项目命名空间和从项目根目录到组件的路径。 例如,框架将具有项目命名空间 BlazorSample
的 {PROJECT NAMESPACE}/Components/Pages/Home.razor
解析到 Home
组件的命名空间 BlazorSample.Components.Pages
。 {PROJECT NAMESPACE}
为项目命名空间。 组件遵循 C# 名称绑定规则。 对于本示例中的 Home
组件,范围内的组件是所有组件:
- 在同一文件夹
Components/Pages
中。
- 未显式指定其他命名空间的项目根中的组件。
不支持以下项目:
global::
限定。
- 部分限定的名称。 例如,无法将
@using BlazorSample
添加到组件中,然后使用 <Layout.NavMenu></Layout.NavMenu>
在应用的 Components/Layout
文件夹中引用 NavMenu
组件 (Components/Layout/NavMenu.razor
)。
组件的名称必须以大写字符开头:
✔️支持:ProductDetail.razor
❌不支持:productDetail.razor
整个 Blazor 文档中使用的常见 Blazor 命名约定包括:
- 文件路径和文件名使用 †Pascal 大小写,且在显示组件代码示例之前出现。 如果路径存在,则表示典型的文件夹位置。 例如,
Pages/ProductDetail.razor
指示 ProductDetail
组件具有文件名 ProductDetail.razor
,并位于应用的 Pages
文件夹中。
- 可路由组件的组件文件路径与其 ‡kebab 大小写形式的 URL 匹配,组件路由模板中各单词之间会显示连字符。 例如,在浏览器中,通过相对 URL
/product-detail
请求具有路由模板 /product-detail
(@page "/product-detail"
) 的 ProductDetail
组件。
†Pascal 大小写(大写 camel 形式)是不带空格和标点符号的命名约定,其中每个单词的首字母大写(包括第一个单词)。
‡Kebab 大小写是一种命名约定,不使用空格和标点符号,它使用小写字母,且单词之间有短划线。
组件是普通 C# 类,可以放置在项目中的任何位置。 生成网页的组件通常位于 Pages
文件夹中。 非页面组件通常放置在 Shared
文件夹或添加到项目的自定义文件夹中。
通常,组件的命名空间是从应用的根命名空间和该组件在应用内的位置(文件夹)派生而来的。 如果应用的根命名空间是 BlazorSample
,并且 Counter
组件位于 Pages
文件夹中:
Counter
组件的命名空间为 BlazorSample.Pages
。
- 组件的完全限定类型名称为
BlazorSample.Pages.Counter
。
对于保存组件的自定义文件夹,将 @using
指令添加到父组件或应用的 _Imports.razor
文件。 下面的示例提供 AdminComponents
文件夹中的组件:
@using BlazorSample.AdminComponents
_Imports.razor
文件中的 @using
指令仅适用于 Razor 文件 (.razor
),而不适用于 C# 文件 (.cs
)。
不支持使用别名的 using
语句。 在以下示例中,GridRendering
组件的公共 WeatherForecast
类作为应用其他位置的组件中的 WeatherForecast
提供:
@using WeatherForecast = Pages.GridRendering.WeatherForecast
还可以使用其完全限定的名称来引用组件,这时不需要 @using
指令。 以下示例直接引用应用的 Components
文件夹中的 ProductDetail
组件:
<BlazorSample.Components.ProductDetail />
使用 Razor 创建的组件的命名空间基于以下内容(按优先级顺序):
- Razor 文件标记中的
@namespace
指令(例如 @namespace BlazorSample.CustomNamespace
)。
- 项目文件中项目的
RootNamespace
(例如 <RootNamespace>BlazorSample</RootNamespace>
)。
- 项目命名空间和从项目根目录到组件的路径。 例如,框架将具有项目命名空间
BlazorSample
的 {PROJECT NAMESPACE}/Pages/Index.razor
解析到 Index
组件的命名空间 BlazorSample.Pages
。 {PROJECT NAMESPACE}
为项目命名空间。 组件遵循 C# 名称绑定规则。 对于本示例中的 Index
组件,范围内的组件是所有组件:
- 在同一文件夹
Pages
中。
- 未显式指定其他命名空间的项目根中的组件。
不支持以下项目:
global::
限定。
- 部分限定的名称。 例如,无法将
@using BlazorSample
添加到组件中,然后使用 <Shared.NavMenu></Shared.NavMenu>
在应用的 Shared
文件夹中引用 NavMenu
组件 (Shared/NavMenu.razor
)。
分部类支持
组件以 C# 分部类的形式生成,使用以下任一方法进行创作:
- 单个文件包含在一个或多个
@code
块、HTML 标记和 Razor 标记中定义的 C# 代码。 Blazor 项目模板使用此单文件方法来定义其组件。
- HTML 和 Razor 标记位于 Razor 文件 (
.razor
) 中。 C# 代码位于定义为分部类的代码隐藏文件 (.cs
) 中。
定义特定于组件的样式的组件样式表是单独的文件 (.css
)。 Blazor CSS 隔离稍后在 ASP.NET Core Blazor CSS 隔离 中进行介绍。
下面的示例显示了从 Blazor 项目模板生成的应用中具有 @code
块的默认 Counter
组件。 标记和 C# 代码位于同一个文件中。 这是在创作组件时采用的最常见方法。
Counter.razor
:
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
currentCount++;
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
currentCount++;
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
currentCount++;
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
currentCount++;
以下 Counter
组件使用带有分部类的代码隐藏文件从 C# 代码中拆分呈现 HTML 和 Razor 标记。 一些组织和开发人员倾向于从 C# 代码拆分标记,借此组织其组件代码来适应他们偏好的工作方式。 例如,组织的 UI 专家可以独自处理呈现层,而不必依赖处理组件的 C# 逻辑的另一位开发人员。 使用自动生成的代码或源生成器时,此方法也很有用。 有关详细信息,请参阅分部类和方法(C# 编程指南)。
CounterPartialClass.razor
:
@page "/counter-partial-class"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
CounterPartialClass.razor.cs
:
namespace BlazorSample.Components.Pages;
public partial class CounterPartialClass
private int currentCount = 0;
private void IncrementCount()
currentCount++;
namespace BlazorSample.Pages;
public partial class CounterPartialClass
private int currentCount = 0;
private void IncrementCount()
currentCount++;
namespace BlazorSample.Pages
public partial class CounterPartialClass
private int currentCount = 0;
private void IncrementCount()
currentCount++;
_Imports.razor
文件中的 @using
指令仅适用于 Razor 文件 (.razor
),而不适用于 C# 文件 (.cs
)。 根据需要将命名空间添加到分部类文件中。
组件使用的典型命名空间:
using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Sections
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
典型命名空间还包含应用的命名空间以及与应用的 Components
文件夹对应的命名空间:
using BlazorSample;
using BlazorSample.Components;
using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
典型命名空间还包含应用的命名空间以及与应用的 Shared
文件夹对应的命名空间:
using BlazorSample;
using BlazorSample.Shared;
using System.Net.Http;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
典型命名空间还包含应用的命名空间以及与应用的 Shared
文件夹对应的命名空间:
using BlazorSample;
using BlazorSample.Shared;
@inherits
指令用于指定组件的基类。 与使用分部类不同,它仅从 C# 逻辑拆分标记,使用基类可以继承 C# 代码,以便在共用基类的属性和方法的一组组件之间使用。 使用基类可减少应用中的代码冗余,在将基代码从类库提供给多个应用时非常有用。 有关详细信息,请参阅 C# 和 .NET 中的继承。
在下面的示例中,BlazorRocksBase
基类派生自 ComponentBase。
BlazorRocks.razor
:
@page "/blazor-rocks"
@inherits BlazorRocksBase
<h1>@BlazorRocksText</h1>
public class BlazorRocksBase : ComponentBase
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
public class BlazorRocksBase : ComponentBase
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
public class BlazorRocksBase : ComponentBase
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
public class BlazorRocksBase : ComponentBase
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
可以通过使用 @page
指令为应用中的每个可访问组件提供路由模板来实现 Blazor 中的路由。 编译具有 @page
指令的 Razor 文件时,将为生成的类提供指定路由模板的 RouteAttribute。 在运行时,路由器将使用 RouteAttribute 搜索组件类,并呈现具有与请求的 URL 匹配的路由模板的任何组件。
以下 HelloWorld
组件使用 /hello-world
的路由模板,并且组件的呈现网页在相对 URL /hello-world
到达。
HelloWorld.razor
:
@page "/hello-world"
<h1>Hello World!</h1>
前面的组件在浏览器中通过 /hello-world
进行加载,无论是否将组件添加到应用的 UI 导航。 (可选)组件可以添加到 NavMenu
组件,以便在应用基于 UI 的导航中显示组件链接。
对于前面的 HelloWorld
组件,可以将 NavLink
组件添加到 NavMenu
组件。 有关详细信息(包括对 NavLink
和 NavMenu
组件的描述),请参阅 ASP.NET Core Blazor 路由和导航。
组件的 UI 使用由 Razor 标记、C# 和 HTML 组成的 Razor 语法进行定义。 在编译应用时,HTML 标记和 C# 呈现逻辑转换为组件类。 生成的类的名称与文件名匹配。
组件类的成员在一个或多个 @code
块中定义。 在 @code
块中,组件状态使用 C# 进行指定和处理:
- 属性和字段初始化表达式。
- 由父组件和路由参数传递的自变量的参数值。
- 用于用户事件处理、生命周期事件和自定义组件逻辑的方法。
组件成员使用以 @
符号开头的 C# 表达式在呈现逻辑中进行使用。 例如,通过为字段名称添加 @
前缀来呈现 C# 字段。 下面 Markup
示例计算并呈现:
headingFontStyle
,表示标题元素的 CSS 属性值 font-style
。
headingText
,表示标题元素的内容。
Markup.razor
:
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
Blazor 文档中的示例会为私有成员指定 private
访问修饰符。 私有成员的范围限定为组件的类。 但是,C# 会在没有访问修饰符存在时采用 private
访问修饰符,因此在自己的代码中将成员显式标记为“private
”是可选的。 有关访问修饰符的详细信息,请参阅访问修饰符(C# 编程指南)。
Blazor 框架在内部将组件作为呈现树进行处理,该树是组件的文档对象模型 (DOM) 和级联样式表对象模型 (CSSOM) 的组合。 最初呈现组件后,会重新生成组件的呈现树以响应事件。 Blazor 会将新呈现树与以前的呈现树进行比较,并将所有修改应用于浏览器的 DOM 以进行显示。 有关详细信息,请参阅 ASP.NET Core Razor 组件呈现。
C# 控件结构、指令和指令属性的 Razor 语法需要小写(示例:@if
、@code
、@bind
)。 属性名称需要大写(示例:LayoutComponentBase.Body 的 @Body
)。
异步方法 (async
) 不支持返回 void
Blazor 框架不跟踪返回 void
的异步方法 (async
)。 因此,如果 void
返回,则不会捕获异常。 始终从异步方法返回 Task。
通过使用 HTML 语法声明组件,组件可以包含其他组件。 使用组件的标记类似于 HTML 标记,其中标记的名称是组件类型。
请考虑以下 Heading
组件,其他组件可以使用该组件显示标题。
Heading.razor
:
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
如果某个组件包含一个 HTML 元素,该元素的大写首字母与相同命名空间中的组件名称不匹配,则会发出警告,指示该元素名称异常。 为组件的命名空间添加 @using
指令使组件可用,这可解决此警告。 有关详细信息,请参阅组件名称、类名和命名空间部分。
此部分中显示的 Heading
组件示例没有 @page
指令,因此用户无法在浏览器中通过直接请求直接访问 Heading
组件。 但是,具有 @page
指令的任何组件都可以嵌套在另一个组件中。 如果通过在 Razor 文件顶部包含 @page "/heading"
可直接访问 Heading
组件,则会在 /heading
和 /heading-example
处为浏览器请求呈现该组件。
组件参数将数据传递给组件,使用组件类中包含 [Parameter]
特性的公共 C# 属性进行定义。 在下面的示例中,内置引用类型 (System.String) 和用户定义的引用类型 (PanelBody
) 作为组件参数进行传递。
PanelBody.cs
:
public class PanelBody
public string? Text { get; set; }
public string? Style { get; set; }
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
Text = "Set by child.",
Style = "normal"
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
Text = "Set by child.",
Style = "normal"
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
Text = "Set by child.",
Style = "normal"
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new PanelBody()
Text = "Set by child.",
Style = "normal"
支持为组件参数提供初始值,但不会创建在首次呈现后向自身参数写入的组件。 有关详细信息,请参阅避免覆盖 ASP.NET Core Blazor 中的参数。
ParameterChild
组件的 Title
和 Body
组件参数通过自变量在呈现组件实例的 HTML 标记中进行设置。 以下 ParameterParent
组件会呈现两个 ParameterChild
组件:
- 第一个
ParameterChild
组件在呈现时不提供参数自变量。
- 第二个
ParameterChild
组件从 ParameterParent
组件接收 Title
和 Body
的值,后者使用显式 C# 表达式设置 PanelBody
的属性值。
ParameterParent.razor
:
@page "/parameter-parent"
<h1>Child component (without attribute values)</h1>
<ParameterChild />
<h1>Child component (with attribute values)</h1>
<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
<ParameterChild Title="Set by Parent"
Body="@(new PanelBody() { Text = "Set by parent.", Style = "italic" })" />
当 ParameterParent
组件未提供组件参数值时,来自 ParameterParent
组件的以下呈现 HTML 标记会显示 ParameterChild
组件默认值。 当 ParameterParent
组件提供组件参数值时,它们会替换 ParameterChild
组件的默认值。
为清楚起见,呈现 CSS 样式类未显示在以下呈现 HTML 标记中。
<h1>Child component (without attribute values)</h1>
<div>Set By Child</div>
<div>Set by child.</div>
<h1>Child component (with attribute values)</h1>
<div>Set by Parent</div>
<div>Set by parent.</div>
将方法的 C# 字段、属性或结果作为 HTML 特性值分配给组件参数。 特性的值通常可以是与参数类型匹配的任何 C# 表达式。 特性的值可以选择以 Razor 保留 @
符号开头,但不是必需的。
如果组件参数的类型为字符串,则默认情况下会将特性值视为 C# 字符串字面量。 如果要改为指定 C# 表达式,请使用 @
前缀。
以下 ParameterParent2
组件显示前面 ParameterChild
组件的四个实例,并将其 Title
参数值设置为:
title
字段的值。
GetTitle
C# 方法的结果。
- 带有ToLongDateString 的长格式当前本地日期,使用隐式 C# 表达式。
panelData
对象的 Title
属性。
我们不建议对文本(例如,布尔值)、关键字(例如,this
)或 null
使用 @
前缀,但可以根据需要选择使用它们。 例如,IsFixed="@true"
不常见,但受支持。
在大多数情况下,根据 HTML5 规范,参数属性值的引号是可选的。 例如,支持 Value=this
,而不是 Value="this"
。 但是,我们建议使用引号,因为它更易于记住,并且在基于 Web 的技术中被广泛采用。
在整个文档中,代码示例:
- 始终使用引号。 示例:
Value="this"
。
- 非文本则使用
@
前缀,尽管这是可选操作。 示例:Count="@ct"
,其中 ct
是数字类型的变量。 Count="ct"
是一种有效的风格方法,但文档和示例不采用约定。
- 对于 Razor 表达式之外的文本始终避免使用
@
。 示例:IsFixed="true"
。
ParameterParent2.razor
:
@page "/parameter-parent-2"
<ParameterChild Title="@title" />
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
return "From Parent method";
private class PanelData
public string Title { get; set; } = "From Parent object";
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
return "From Parent method";
private class PanelData
public string Title { get; set; } = "From Parent object";
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new();
private string GetTitle()
return "From Parent method";
private class PanelData
public string Title { get; set; } = "From Parent object";
<ParameterChild Title="@GetTitle()" />
<ParameterChild Title="@DateTime.Now.ToLongDateString()" />
<ParameterChild Title="@panelData.Title" />
@code {
private string title = "From Parent field";
private PanelData panelData = new PanelData();
private string GetTitle()
return "From Parent method";
private class PanelData
public string Title { get; set; } = "From Parent object";
将 C# 成员分配给组件参数时,不要使用 @
为参数的 HTML 特性添加前缀。
正确(Title
是字符串参数, Count
是数字类型的参数):
<ParameterChild Title="@title" Count="@ct" />
<ParameterChild Title="@title" Count="ct" />
<ParameterChild @Title="@title" @Count="@ct" />
<ParameterChild @Title="@title" @Count="ct" />
与 Razor 页面 (.cshtml
) 不同,在呈现组件时,Blazor 不能在 Razor 表达式中执行异步工作。 这是因为 Blazor 是为呈现交互式 UI 而设计的。 在交互式 UI 中,屏幕必须始终显示某些内容,因此阻止呈现流是没有意义的。 相反,异步工作是在一个异步生命周期事件期间执行的。 在每个异步生命周期事件之后,组件可能会再次呈现。 不支持以下 Razor 语法:
<ParameterChild Title="@await ..." />
生成应用时,前面示例中的代码会生成编译器错误:
“await”运算符只能用于异步方法中。 请考虑用“async”修饰符标记此方法,并将其返回类型更改为“Task”。
若要在前面的示例中异步获取 Title
参数的值,组件可以使用 OnInitializedAsync
生命周期事件,如以下示例所示:
<ParameterChild Title="@title" />
@code {
private string? title;
protected override async Task OnInitializedAsync()
title = await ...;
有关详细信息,请参阅 ASP.NET Core Razor 组件生命周期。
不支持使用显式 Razor 表达式连接文本和表达式结果以赋值给参数。 下面的示例尝试将文本“Set by
”与对象属性值连接在一起。 尽管 Razor 页面 (.cshtml
) 支持此语法,但对在组件中赋值给子级的 Title
参数无效。 不支持以下 Razor 语法:
<ParameterChild Title="Set by @(panelData.Title)" />
生成应用时,前面示例中的代码会生成编译器错误:
组件属性不支持复杂内容(混合 C# 和标记)。
若要支持组合值赋值,请使用方法、字段或属性。 下面的示例在 C# 方法 GetTitle
中将“Set by
”与对象属性值连接在一起:
ParameterParent3.razor
:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
public string Title { get; set; } = "Parent";
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
public string Title { get; set; } = "Parent";
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
public string Title { get; set; } = "Parent";
private PanelData panelData = new PanelData();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
public string Title { get; set; } = "Parent";
有关详细信息,请参阅 ASP.NET Core 的 Razor 语法参考。
支持为组件参数提供初始值,但不会创建在首次呈现后向自身参数写入的组件。 有关详细信息,请参阅避免覆盖 ASP.NET Core Blazor 中的参数。
应将组件参数声明为自动属性,这意味着它们不应在其 get
或 set
访问器中包含自定义逻辑。 例如,下面的 StartData
属性是自动属性:
[Parameter]
public DateTime StartData { get; set; }
不要在 get
或 set
访问器中放置自定义逻辑,因为组件参数专门用作父组件向子组件传送信息的通道。 如果子组件属性的 set
访问器包含导致父组件重新呈现的逻辑,则会导致一个无限的呈现循环。
若要转换已接收的参数值,请执行以下操作:
- 将参数属性保留为自动属性,以表示所提供的原始数据。
- 创建另一个属性或方法,用于基于参数属性提供转换后的数据。
替代 OnParametersSetAsync
以在每次收到新数据时转换接收到的参数。
支持将初始值写入组件参数,因为初始值赋值不会干扰 Blazor 的自动组件呈现。 在组件中,使用 DateTime.Now 将当前本地 DateTime 赋予 StartData
是有效语法:
[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;
进行 DateTime.Now 的初始赋值之后,请勿在开发人员代码中向 StartData
赋值。 有关详细信息,请参阅避免覆盖 ASP.NET Core Blazor 中的参数。
应用 [EditorRequired]
特性以指定所需的组件参数。 如果未提供参数值,编辑器或生成工具可能会向用户显示警告。 此特性仅在也用 [Parameter]
特性标记的属性上有效。 在设计时和生成应用时需强制使用 EditorRequiredAttribute。 在运行时则不强制使用该特性,因为它无法保证非 null
的参数值。
[Parameter]
[EditorRequired]
public string? Title { get; set; }
还支持单行特性列表:
[Parameter, EditorRequired]
public string? Title { get; set; }
不要对组件参数属性使用 required
修饰符 或 init
访问器。 组件通常使用反射实例化和分配参数值,从而绕过了 init
和 required
本应做出的保证。 请改为使用 [EditorRequired]
特性以指定所需的组件参数。
不要对组件参数属性使用 init
访问器,因为设置具有 ParameterView.SetParameterProperties 的组件参数值会使用反射,这会绕过仅初始化的资源库限制。 应用 [EditorRequired]
特性以指定所需的组件参数。
不要对组件参数属性使用 init
访问器,因为设置具有 ParameterView.SetParameterProperties 的组件参数值会使用反射,这会绕过仅初始化的资源库限制。
组件参数和 RenderFragment
类型支持 Tuples
(API 文档)。 下面的组件参数示例在 Tuple
中传递三个值:
RenderTupleChild.razor
:
<div class="card w-50" style="margin-bottom:15px">
<div class="card-header font-weight-bold"><code>Tuple</code> Card</div>
<div class="card-body">
<li>Integer: @Data?.Item1</li>
<li>String: @Data?.Item2</li>
<li>Boolean: @Data?.Item3</li>
@code {
[Parameter]
public (int, string, bool)? Data { get; set; }
RenderTupleParent.razor
:
@page "/render-tuple-parent"
<h1>Render Tuple Parent</h1>
<RenderTupleChild Data="@data" />
@code {
private (int, string, bool) data = new(999, "I aim to misbehave.", true);
支持命名元组,如以下示例所示:
RenderNamedTupleChild.razor
:
<div class="card w-50" style="margin-bottom:15px">
<div class="card-header font-weight-bold"><code>Tuple</code> Card</div>
<div class="card-body">
<li>Integer: @Data?.TheInteger</li>
<li>String: @Data?.TheString</li>
<li>Boolean: @Data?.TheBoolean</li>
@code {
[Parameter]
public (int TheInteger, string TheString, bool TheBoolean)? Data { get; set; }
RenderNamedTupleParent.razor
:
@page "/render-named-tuple-parent"
<h1>Render Named Tuple Parent</h1>
<RenderNamedTupleChild Data="@data" />
@code {
private (int TheInteger, string TheString, bool TheBoolean) data =
new(999, "I aim to misbehave.", true);
引用 ©2005 环球影业:冲出宁静号(内森·菲利安)
组件可以在 @page
指令的路由模板中指定路由参数。 Blazor 路由器使用路由参数来填充相应的组件参数。
支持可选路由参数。 在下面的示例中,text
可选参数将 route 段的值赋给组件的 Text
属性。 如果该段不存在,则在 OnInitialized
生命周期方法中将 Text
的值设置为 fantastic
。
不支持可选路由参数,因此在下面的示例中应用了两个 @page
指令。 第一个 @page
指令允许导航到没有路由参数的组件。 第二个 @page
指令接收 {text}
路由参数,并将值分配给 Text
属性。
RouteParameter.razor
:
@page "/route-parameter/{text?}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string? Text { get; set; }
protected override void OnInitialized()
Text = Text ?? "fantastic";
有关捕获跨越多个文件夹边界的路径的 catch-all 路由参数 ({*pageRoute}
) 的信息,请参阅 ASP.NET Core Blazor 路由和导航。
子内容呈现片段
组件可以设置另一个组件的内容。 分配组件提供子组件的开始标记与结束标记之间的内容。
在下面的示例中,RenderFragmentChild
组件具有一个 ChildContent
组件参数,它将要呈现的 UI 段表示为 RenderFragment。 ChildContent
在组件 Razor 标记中的位置是在最终 HTML 输出中呈现内容的位置。
RenderFragmentChild.razor
:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
必须按约定将接收 RenderFragment 内容的属性命名为 ChildContent
。
RenderFragment 不支持事件回叫。
以下 RenderFragmentParent
组件通过将内容置于子组件的开始标记和结束标记内,来提供用于呈现 RenderFragmentChild
的内容。
RenderFragmentParent.razor
:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>
由于 Blazor 呈现子内容的方式,如果在 RenderFragmentChild
组件的内容中使用递增循环变量,则在 for
循环内呈现组件需要本地索引变量。 下面的示例可以添加到前面的 RenderFragmentParent
组件:
<h1>Three children with an index variable</h1>
@for (int c = 0; c < 3; c++)
var current = c;
<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
或者,将 foreach
循环与 Enumerable.Range 结合使用,而不是使用 for
循环。 下面的示例可以添加到前面的 RenderFragmentParent
组件:
<h1>Second example of three children with an index variable</h1>
@foreach (var c in Enumerable.Range(0,3))
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
呈现片段用于在整个 Blazor 应用中呈现子内容,在下面的文章和文章部分中有示例介绍:
- Blazor 布局
- 跨组件层次结构传递数据
- 模板化组件
- 全局异常处理
Blazor 框架的内置 Razor 组件使用相同的 ChildContent
组件参数约定来设置其内容。 可以通过在 API 文档(使用搜索词“ChildContent”筛选 API)中搜索组件参数属性名称 ChildContent
来查看设置子内容的组件。
可重用呈现逻辑的呈现片段
你可以分解出子组件,纯粹作为重复使用呈现逻辑的方法。 在任何组件的 @code
块中,根据需要定义 RenderFragment 并呈现任意位置的片段:
@RenderWelcomeInfo
<p>Render the welcome info a second time:</p>
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!</p>;
有关详细信息,请参阅重复使用呈现逻辑。
捕获对组件的引用
组件引用提供了一种引用组件实例以便发出命令的方法。 若要捕获组件引用,请执行以下操作:
- 向子组件添加
@ref
特性。
- 定义与子组件类型相同的字段。
呈现组件时,将用组件实例填充字段。 然后,可以在实例上调用 .NET 方法。
请考虑以下 ReferenceChild
组件,它会在调用其 ChildMethod
时记录消息。
ReferenceChild.razor
:
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> Logger
@code {
public void ChildMethod(int value)
Logger.LogInformation("Received {Value} in ChildMethod", value);
public void ChildMethod(int value)
Logger.LogInformation("Received {Value} in ChildMethod", value);
public void ChildMethod(int value)
Logger.LogInformation("Received {Value} in ChildMethod", value);
public void ChildMethod(int value)
Logger.LogInformation("Received {Value} in ChildMethod", value);
组件引用仅在呈现组件后才进行填充,其输出包含 ReferenceChild
的元素。 在呈现组件之前,没有任何可引用的内容。
若要在组件完成呈现后操作组件引用,请使用 OnAfterRender
或 OnAfterRenderAsync
方法。
若要结合使用事件处理程序和引用变量,请使用 Lambda 表达式,或在 OnAfterRender
或 OnAfterRenderAsync
方法中分配事件处理程序委托。 这可确保在分配事件处理程序之前先分配引用变量。
以下 lambda 方法使用前面的 ReferenceChild
组件。
ReferenceParent1.razor
:
@page "/reference-parent-1"
<button @onclick="@(() => childComponent?.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
<button @onclick="@(() => childComponent?.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
<button @onclick="@(() => childComponent.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
<button @onclick="@(() => childComponent.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
以下委托方法使用前面的 ReferenceChild
组件。
ReferenceParent2.razor
:
@page "/reference-parent-2"
<button @onclick="@(() => callChildMethod?.Invoke())">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;
protected override void OnAfterRender(bool firstRender)
if (firstRender)
callChildMethod = CallChildMethod;
private void CallChildMethod()
childComponent?.ChildMethod(5);
<button @onclick="@(() => callChildMethod?.Invoke())">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;
protected override void OnAfterRender(bool firstRender)
if (firstRender)
callChildMethod = CallChildMethod;
private void CallChildMethod()
childComponent?.ChildMethod(5);
<button @onclick="callChildMethod">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
private Action callChildMethod;
protected override void OnAfterRender(bool firstRender)
if (firstRender)
callChildMethod = CallChildMethod;
private void CallChildMethod()
childComponent.ChildMethod(5);
<button @onclick="callChildMethod">
Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>
<ReferenceChild @ref="childComponent" />
@code {
private ReferenceChild childComponent;
private Action callChildMethod;
protected override void OnAfterRender(bool firstRender)
if (firstRender)
callChildMethod = CallChildMethod;
private void CallChildMethod()
childComponent.ChildMethod(5);
尽管捕获组件引用使用与捕获元素引用类似的语法,但捕获组件引用不是 JavaScript 互操作功能。 组件引用不会传递给 JavaScript 代码。 组件引用只在 .NET 代码中使用。
不要使用组件引用来改变子组件的状态。 请改用常规声明性组件参数将数据传递给子组件。 使用组件参数使子组件在正确的时间自动重新呈现。 有关详细信息,请参阅组件参数部分和 ASP.NET Core Blazor 数据绑定一文。
可以通过 @attribute
指令将特性应用于组件。 下面的示例将 [Authorize]
特性应用于组件的类:
@page "/"
@attribute [Authorize]
条件 HTML 元素属性
HTML 元素特性属性基于 .NET 值有条件地设置。 如果值为 false
或 null
,则属性未设置。 如果值为 true
,则属性已设置。
在下面的示例中,IsCompleted
确定是否设置了 <input>
元素的 checked
属性。
ConditionalAttribute.razor
:
@page "/conditional-attribute"
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
<button @onclick="@(() => IsCompleted = !IsCompleted)">
Change IsCompleted
</button>
@code {
[Parameter]
public bool IsCompleted { get; set; }
当 .NET 类型为 bool
时,某些 HTML 特性(如 aria-pressed
)无法正常运行。 在这些情况下,请使用 string
类型,而不是 bool
。
原始 HTML
通常使用 DOM 文本节点呈现字符串,这意味着将忽略它们可能包含的任何标记,并将其视为文字文本。 若要呈现原始 HTML,请将 HTML 内容包装在 MarkupString 值中。 将该值分析为 HTML 或 SVG,并插入到 DOM 中。
呈现从任何不受信任的源构造的原始 HTML 存在安全风险,应始终避免。
下面的示例演示如何使用 MarkupString 类型向组件的呈现输出添加静态 HTML 内容块。
MarkupStringExample.razor
:
@page "/markup-string-example"
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
Razor 模板
可以使用 Razor 模板语法定义呈现片段,从而定义 UI 片段。 Razor 模板使用以下格式:
@<{HTML tag}>...</{HTML tag}>
下面的示例演示如何在组件中指定 RenderFragment 和 RenderFragment<TValue> 值并直接呈现模板。 还可以将呈现片段作为参数传递给模板化组件。
RazorTemplate.razor
:
@page "/razor-template"
@timeTemplate
@petTemplate(new Pet { Name = "Nutty Rex" })
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
public string? Name { get; set; }
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
public string? Name { get; set; }
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
public string Name { get; set; }
@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;
private class Pet
public string Name { get; set; }
以上代码的呈现输出:
<p>The time is 4/19/2021 8:54:46 AM.</p>
<p>Pet: Nutty Rex</p>
Blazor 遵循 ASP.NET Core 应用对于静态资产的约定。 静态资产位于项目的 web root
(wwwroot
) 文件夹中或是 wwwroot
文件夹下的文件夹中。
使用基相对路径 (/
) 来引用静态资产的 Web 根。 在下面的示例中,logo.png
实际位于 {PROJECT ROOT}/wwwroot/images
文件夹中。 {PROJECT ROOT}
是应用的项目根。
<img alt="Company logo" src="/images/logo.png" />
组件不支持波浪符斜杠表示法 (~/
)。
有关设置应用基本路径的信息,请参阅托管和部署 ASP.NET Core Blazor。
组件中不支持标记帮助程序
组件中不支持 Tag Helpers
。 若要在 Blazor 中提供类似标记帮助程序的功能,请创建一个具有与标记帮助程序相同功能的组件,并改为使用该组件。
可缩放的向量图形 (SVG) 图像
由于 Blazor 呈现 HTML,因此通过 <img>
标记支持浏览器支持的图像,包括可缩放的矢量图形 (SVG) 图像 (.svg
):
<img alt="Example image" src="image.svg" />
同样,样式表文件 (.css
) 的 CSS 规则支持 SVG 图像:
.element-class {
background-image: url("image.svg");
Blazor 支持 <foreignObject>
元素在 SVG 中显示任意 HTML。 标记可表示任意 HTML、RenderFragment 或 Razor 组件。
下面的示例展示了如何:
string
(@message
) 的显示情况。
- 具有
<input>
元素和 value
字段的双向绑定。
Robot
组件。
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" rx="10" ry="10" width="200" height="200" stroke="black"
fill="none" />
<foreignObject x="20" y="20" width="160" height="160">
<p>@message</p>
</foreignObject>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<label>
Two-way binding:
<input @bind="value" @bind:event="oninput" />
</label>
</foreignObject>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<Robot />
</foreignObject>
@code {
private string message = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
private string? value;
空白的呈现行为
除非将 @preservewhitespace
指令与值 true
一起使用,否则在以下情况下默认删除额外的空白:
- 元素中的前导或尾随空白。
- RenderFragment/RenderFragment<TValue> 参数中的前导或尾随(例如,传递到另一个组件的子内容)。
- 在 C# 代码块(例如
@if
和 @foreach
)之前或之后。
但是,在使用 CSS 规则(例如 white-space: pre
)时,删除空白可能会影响呈现输出。 若要禁用此性能优化并保留空白,请执行以下任一操作:
- 将
@preservewhitespace true
指令添加到 Razor 文件 (.razor
) 的顶部,从而将首选项应用于特定组件。
- 将
@preservewhitespace true
指令添加到 _Imports.razor
文件中,从而将首选项应用于子目录或整个项目。
在大多数情况下,不需要执行任何操作,因为应用程序通常会继续正常运行(但速度会更快)。 如果去除空白会导致特定组件出现呈现问题,请在该组件中使用 @preservewhitespace true
来禁用此优化。
空白将保留在组件的源标记中。 即使在没有视觉效果的情况下,只有空白的文本也会呈现在浏览器的 DOM 中。
请考虑以下组件标记:
@foreach (var item in Items)
@item.Text
前面的示例呈现以下不必要的空白:
- 在
@foreach
代码块外。
- 围绕
<li>
元素。
- 围绕
@item.Text
输出。
100 项的列表会导致超过 400 个空白区域。 任何额外空白都不会在视觉上影响呈现的输出。
呈现组件的静态 HTML 时,不会保留标记中的空白。 例如,查看组件 Razor 文件 (.razor
) 中以下 <img>
标记的呈现输出:
<img alt="Example image" src="img.png" />
不会保留前面标记中的空白:
<img alt="Example image" src="img.png" />
呈现静态根 Razor 组件
根 Razor 组件是加载应用创建的任何组件层次结构的第一个组件。
在从 Blazor Web App 项目模板创建的应用中,App
组件 (App.razor
) 由向服务器侧 Program
文件调用 MapRazorComponents<TRootComponent>
声明的类型参数指定为默认根组件。 以下示例演示如何使用 App
组件作为根组件,这是从 Blazor 项目模板创建的应用的默认组件:
app.MapRazorComponents<App>();
在从 Blazor Server 项目模板创建的应用中,使用 组件标记帮助程序将 App
组件 (App.razor
) 指定为 Pages/_Host.cshtml
中的默认根组件:
<component type="typeof(App)" render-mode="ServerPrerendered" />
在从 Blazor WebAssembly 项目模板创建的应用中,将 App
组件 (App.razor
) 指定为 Program
文件中的默认根组件:
builder.RootComponents.Add<App>("#app");
在前面的代码中,CSS 选择器 #app
指示为 wwwroot/index.html
中的 <div>
指定了 App
组件,其中 id
为 app
:
<div id="app">...</app>
MVC 和 Razor Pages 应用还可以使用组件标记帮助程序来注册静态呈现的 Blazor WebAssembly 根组件:
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
静态呈现的组件只能添加到应用。 之后无法移除或更新它们。
有关更多信息,请参见以下资源:
- ASP.NET Core 中的组件标记帮助程序
- 预呈现和集成 ASP.NET Core Razor 组件