ASP.NET Core Razor 组件
说明
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
本文介绍如何在 Blazor 应用中创建和使用 Razor 组件,包括有关 Razor 语法、组件命名、命名空间和组件参数的指导。
Razor 组件
Blazor 应用是使用 Razor 组件(非正式地称为 Blazor 组件或组件)构建的。 组件是用户界面 (UI) 的自包含部分,具有用于启用动态行为的处理逻辑。 组件可以嵌套、重用、在项目间共享,并可在 MVC 和 Razor Pages 应用中使用。
组件呈现为浏览器文档对象模型 (DOM) 的内存中表现形式,它被称为“呈现树”,用于以灵活高效的方式更新 UI。
尽管“Razor 组件”与其他 ASP.NET Core 内容呈现技术共享某种命名,但 Razor 组件必须与 ASP.NET Core 中的以下不同功能区分开来:
- Razor 视图,它们是 MVC 应用的基于 Razor 的标记页面。
- 视图组件,用于在 Razor Pages 和 MVC 应用中呈现内容块而不是整个响应。
重要
使用 Blazor Web App 时,大多数 Blazor 文档示例组件需要交互性才能运行并演示文章中涵盖的概念。 测试某篇文章提供的示例组件时,请确保应用采用全局交互性或组件采用交互式呈现模式。 有关此主题的详细信息,请参阅 ASP.NET Core Blazor 呈现模式,即目录中本文后面的下一篇文章。
组件类
组件是使用 C# 和 HTML 标记的组合在 Razor 组件文件(文件扩展名为 .razor
)中实现的。
ComponentBase 是 Razor 组件文件描述的组件的基类。 ComponentBase 实现组件的最低抽象,IComponent 接口。 ComponentBase 定义基本功能的组件属性和方法,例如,处理一组内置组件生命周期事件。
dotnet/aspnetcore
引用源中的 ComponentBase
:引用源包含有关内置生命周期事件的其他注释。 但是请记住,组件功能的内部实现随时可能会更改,但不会发出通知。
说明
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
开发人员通常从 Razor 组件文件 (.razor
) 创建 Razor 组件,或将组件构建于 ComponentBase 之上,但组件也可以通过实现 IComponent 来生成。 实现 IComponent 的开发人员所构建的组件可以对呈现采用低级别控制,代价是需要使用事件和生命周期方法手动触发呈现,且这些事件和方法必须由开发人员创建和维护。
ASP.NET Core Blazor 基础知识中提供了 Blazor 文档示例代码和示例应用采用的其他约定。
Razor 语法
组件使用 Razor 语法。 组件广泛使用了两个 Razor 功能,即指令和指令特性。 这两个功能是前缀为 @
的保留关键字,出现在 Razor 标记中:
指令:更改组件标记的编译或运行方式。 例如,
@page
指令会指定具有路由模板的可路由组件,用户在浏览器中通过特定 URL 发出请求即可直接访问。按照约定,组件定义(
.razor
文件)顶部的组件指令按一致顺序排列。 对于重复指令,指令按命名空间或类型按字母顺序放置,但具有特殊二级排序的@using
指令除外。Blazor 示例应用和文档采用以下顺序。 Blazor 项目模板提供的组件可能与以下顺序不同,并使用不同的格式。 例如,Blazor 框架 Identity 组件包括指令块
@using
和指令块@inject
之间的空白行。 你可以在自己的应用中自由使用自定义排序方案和格式。文档和示例应用 Razor 指令顺序:
@page
@rendermode
(.NET 8 或更高版本)@using
System
命名空间(字母顺序)Microsoft
命名空间(字母顺序)- 第三方 API 命名空间(字母顺序)
- 应用命名空间(字母顺序)
- 其他指令(字母顺序)
指令之间没有出现空白行。 指令与 Razor 标记的第一行之间会出现一个空白行。
示例:
@page "/doctor-who-episodes/{season:int}" @rendermode InteractiveWebAssembly @using System.Globalization @using System.Text.Json @using Microsoft.AspNetCore.Localization @using Mandrill @using BlazorSample.Components.Layout @attribute [Authorize] @implements IAsyncDisposable @inject IJSRuntime JS @inject ILogger<DoctorWhoEpisodes> Logger <PageTitle>Doctor Who Episode List</PageTitle> ...
指令特性:更改组件元素的编译方式或运行方式。
示例:
<input @bind="episodeId" />
对于非显式Razor表达式 (
@bind="@episodeId"
),可以使用 at 符号 (@
) 为指令属性值添加前缀,但不建议使用,并且文档不会采用示例中的方法。
本文和 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.Components
添加到组件中,然后使用<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++;
}
@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++;
}
}
@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++;
}
}
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
@page "/counter"
<h1>Counter</h1>
<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>
@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>
@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>
@page "/counter-partial-class"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@page "/counter-partial-class"
<h1>Counter</h1>
<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 static Microsoft.AspNetCore.Components.Web.RenderMode;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;
典型命名空间还包含应用的命名空间以及与应用的 Components
文件夹对应的命名空间:
using BlazorSample;
using BlazorSample.Components;
还可以包含其他文件夹,例如 Layout
文件夹:
using BlazorSample.Components.Layout;
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 中的继承。
在下面的示例中,BlazorRocksBase1
基类派生自 ComponentBase。
BlazorRocks1.razor
:
@page "/blazor-rocks-1"
@inherits BlazorRocksBase1
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 1</h1>
<p>
@BlazorRocksText
</p>
@page "/blazor-rocks-1"
@inherits BlazorRocksBase1
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 1</h1>
<p>
@BlazorRocksText
</p>
@page "/blazor-rocks-1"
@inherits BlazorRocksBase1
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 1</h1>
<p>
@BlazorRocksText
</p>
@page "/blazor-rocks-1"
@inherits BlazorRocksBase1
<h1>Blazor Rocks! Example 1</h1>
<p>
@BlazorRocksText
</p>
@page "/blazor-rocks-1"
@inherits BlazorRocksBase1
<h1>Blazor Rocks! Example 1</h1>
<p>
@BlazorRocksText
</p>
BlazorRocksBase1.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase1 : ComponentBase
{
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase1 : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase1 : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase1 : ComponentBase
{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase1 : 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"
<PageTitle>Hello World!</PageTitle>
<h1>Hello World!</h1>
@page "/hello-world"
<h1>Hello World!</h1>
@page "/hello-world"
<h1>Hello World!</h1>
@page "/hello-world"
<h1>Hello World!</h1>
@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"
<PageTitle>Markup</PageTitle>
<h1>Markup Example</h1>
<h2 style="font-style:@headingFontStyle">@headingText</h2>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}
@page "/markup"
<h1 style="font-style:@headingFontStyle">@headingText</h1>
@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";
}
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
<h1 style="font-style:@headingFontStyle">Heading Example</h1>
@code {
private string headingFontStyle = "italic";
}
HeadingExample
组件中的以下标记会在 <Heading />
标记出现的位置呈现前面的 Heading
组件。
HeadingExample.razor
:
@page "/heading-example"
<PageTitle>Heading</PageTitle>
<h1>Heading Example</h1>
<Heading />
@page "/heading-example"
<Heading />
@page "/heading-example"
<Heading />
@page "/heading-example"
<Heading />
@page "/heading-example"
<Heading />
如果某个组件包含一个 HTML 元素,该元素的大写首字母与相同命名空间中的组件名称不匹配,则会发出警告,指示该元素名称异常。 为组件的命名空间添加 @using
指令使组件可用,这可解决此警告。 有关详细信息,请参阅组件名称、类名和命名空间部分。
此部分中显示的 Heading
组件示例没有 @page
指令,因此用户无法在浏览器中通过直接请求直接访问 Heading
组件。 但是,具有 @page
指令的任何组件都可以嵌套在另一个组件中。 如果通过在 Razor 文件顶部包含 @page "/heading"
可直接访问 Heading
组件,则会在 /heading
和 /heading-example
处为浏览器请求呈现该组件。
组件参数
组件参数将数据传递给组件,使用组件类中包含 [Parameter]
特性的公共 C# 属性进行定义。 在下面的示例中,内置引用类型 (System.String) 和用户定义的引用类型 (PanelBody
) 作为组件参数进行传递。
PanelBody.cs
:
namespace BlazorSample;
public class PanelBody
{
public string? Text { get; set; }
public string? Style { get; set; }
}
public class PanelBody
{
public string? Text { get; set; }
public string? Style { get; set; }
}
public class PanelBody
{
public string? Text { get; set; }
public string? Style { get; set; }
}
public class PanelBody
{
public string Text { get; set; }
public string Style { get; set; }
}
public class PanelBody
{
public string Text { get; set; }
public string Style { get; set; }
}
ParameterChild.razor
:
<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
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = "Set By Child";
[Parameter]
public PanelBody Body { get; set; } =
new()
{
Text = "Card content 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
</div>
</div>
@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
</div>
</div>
@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
</div>
</div>
@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
</div>
</div>
@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
的属性值。
Parameter1.razor
:
@page "/parameter-1"
<PageTitle>Parameter 1</PageTitle>
<h1>Parameter Example 1</h1>
<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" })" />
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" })" />
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" })" />
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" })" />
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" })" />
当 ParameterParent
组件未提供组件参数值时,来自 ParameterParent
组件的以下呈现 HTML 标记会显示 ParameterChild
组件默认值。 当 ParameterParent
组件提供组件参数值时,它们会替换 ParameterChild
组件的默认值。
说明
为清楚起见,呈现 CSS 样式类未显示在以下呈现 HTML 标记中。
<h1>Child component (without attribute values)</h1>
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<h1>Child component (with attribute values)</h1>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
将方法的 C# 字段、属性或结果作为 HTML 特性值分配给组件参数。 特性的值通常可以是与参数类型匹配的任何 C# 表达式。 特性的值可以选择以 Razor 保留 @
符号开头,但不是必需的。
如果组件参数的类型为字符串,则会改为将特性值视为 C# 字符串字面量。 如果要改为指定 C# 表达式,请使用 @
前缀。
以下 ParameterParent2
组件显示前面 ParameterChild
组件的四个实例,并将其 Title
参数值设置为:
title
字段的值。GetTitle
C# 方法的结果。- 带有ToLongDateString 的长格式当前本地日期,使用隐式 C# 表达式。
panelData
对象的Title
属性。
在大多数情况下,根据 HTML5 规范,参数属性值的引号是可选的。 例如,支持 Value=this
,而不是 Value="this"
。 但是,我们建议使用引号,因为它更易于记住,并且在基于 Web 的技术中被广泛采用。
在整个文档中,代码示例:
- 始终使用引号。 示例:
Value="this"
。 - 除非必要,否则不要将
@
前缀与非文本一起使用。 示例:Count="ct"
,其中ct
是数字类型的变量。Count="@ct"
是一种有效的风格方法,但文档和示例不采用约定。 - 对于 Razor 表达式之外的文本始终避免使用
@
。 示例:IsFixed="true"
。 这包括关键字(例如this
)和null
,但你可以根据需要选择使用它们。 例如,IsFixed="@true"
不常见,但受支持。
Parameter2.razor
:
@page "/parameter-2"
<PageTitle>Parameter 2</PageTitle>
<h1>Parameter Example 2</h1>
<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() => "From Parent method";
private class PanelData
{
public string Title { get; set; } = "From Parent object";
}
}
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";
}
}
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";
}
}
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 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 ..." />
<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
”与对象属性值连接在一起:
Parameter3.razor
:
@page "/parameter-3"
<PageTitle>Parameter 3</PageTitle>
<h1>Parameter Example 3</h1>
<ParameterChild Title="@GetTitle()" />
@code {
private PanelData panelData = new();
private string GetTitle() => $"Set by {panelData.Title}";
private class PanelData
{
public string Title { get; set; } = "Parent";
}
}
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";
}
}
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";
}
}
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";
}
}
ParameterParent3.razor
:
@page "/parameter-parent-3"
<ParameterChild Title="@GetTitle()" />
@code {
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">Tuple Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.Item1</li>
<li>String: @Data?.Item2</li>
<li>Boolean: @Data?.Item3</li>
</ul>
</div>
</div>
@code {
[Parameter]
public (int, string, bool)? Data { get; set; }
}
RenderTupleParent.razor
:
@page "/render-tuple-parent"
<PageTitle>Render Tuple Parent</PageTitle>
<h1>Render Tuple Parent Example</h1>
<RenderTupleChild Data="data" />
@code {
private (int, string, bool) data = new(999, "I aim to misbehave.", true);
}
支持命名元组,如以下示例所示:
NamedTupleChild.razor
:
<div class="card w-50" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Tuple Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.TheInteger</li>
<li>String: @Data?.TheString</li>
<li>Boolean: @Data?.TheBoolean</li>
</ul>
</div>
</div>
@code {
[Parameter]
public (int TheInteger, string TheString, bool TheBoolean)? Data { get; set; }
}
NamedTuples.razor
:
@page "/named-tuples"
<PageTitle>Named Tuples</PageTitle>
<h1>Named Tuples Example</h1>
<NamedTupleChild Data="data" />
@code {
private (int TheInteger, string TheString, bool TheBoolean) data =
new(999, "I aim to misbehave.", true);
}
组件参数和 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">
<ul>
<li>Integer: @Data?.Item1</li>
<li>String: @Data?.Item2</li>
<li>Boolean: @Data?.Item3</li>
</ul>
</div>
</div>
@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">
<ul>
<li>Integer: @Data?.TheInteger</li>
<li>String: @Data?.TheString</li>
<li>Boolean: @Data?.TheBoolean</li>
</ul>
</div>
</div>
@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);
}
路由参数
组件可以在 @page
指令的路由模板中指定路由参数。 Blazor 路由器使用路由参数来填充相应的组件参数。
RouteParameter1.razor
:
@page "/route-parameter-1/{text}"
<PageTitle>Route Parameter 1</PageTitle>
<h1>Route Parameter Example 1</h1>
<p>Blazor is @Text!</p>
@code {
[Parameter]
public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string? Text { get; set; }
}
@page "/route-parameter-1/{text}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string Text { get; set; }
}
@page "/route-parameter-1/{text}"
<h1>Blazor is @Text!</h1>
@code {
[Parameter]
public string Text { get; set; }
}
有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航的“路由参数”部分。 另外还支持可选路由参数,它们在同一部分中有介绍。 有关捕获跨越多个文件夹边界的路径的 catch-all 路由参数 ({*pageRoute}
) 的信息,请参阅 ASP.NET Core Blazor 路由和导航的“Catch-all 路由参数”部分。
有关详细信息,请参阅 ASP.NET Core Blazor 路由和导航的“路由参数”部分。 不支持可选路由参数,因此需要两个 @page
指令(有关详细信息,请参阅路由参数部分)。 有关捕获跨越多个文件夹边界的路径的 catch-all 路由参数 ({*pageRoute}
) 的信息,请参阅 ASP.NET Core Blazor 路由和导航的“Catch-all 路由参数”部分。
警告
使用默认启用的压缩时,避免创建安全(经过身份验证/授权)的交互式服务器端组件来呈现来自不受信任的源的数据。 不受信任的源包括路由参数、查询字符串、来自 JS 互操作的数据,以及第三方用户可以控制的任何其他数据源(数据库、外部服务)。 有关详细信息,请参阅 ASP.NET Core BlazorSignalR 指南和 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>
</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>
</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>
</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>
</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>
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
以下组件通过将内容置于子组件的开始标记和结束标记内,来提供用于呈现 RenderFragmentChild
的内容。
RenderFragments.razor
:
@page "/render-fragments"
<PageTitle>Render Fragments</PageTitle>
<h1>Render Fragments Example</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</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>
RenderFragmentParent.razor
:
@page "/render-fragment-parent"
<h1>Render child content</h1>
<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</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>
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 应用中呈现子内容,在下面的文章和文章部分中有示例介绍:
说明
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>;
}
有关详细信息,请参阅重复使用呈现逻辑。
带有组件参数和子内容的循环变量
如果递增循环变量由组件的参数或 RenderFragment 子内容使用,则在 for
循环内呈现组件需要本地索引变量。
请考虑以下 RenderFragmentChild2
组件,该组件同时具有组件参数 (Id
) 和用于显示子内容的呈现片段 (ChildContent
)。
RenderFragmentChild2.razor
:
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">Child content (@Id)</div>
<div class="card-body">@ChildContent</div>
</div>
@code {
[Parameter]
public string? Id { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
如果在父组件中呈现 RenderFragmentChild2
组件,在分配组件参数值并提供子组件的内容时,请使用本地索引变量(下面示例中的 ct
)而不是循环变量 (c
):
@for (int c = 1; c < 4; c++)
{
var ct = c;
<RenderFragmentChild2 Id="@($"Child{ct}")">
Count: @ct
</RenderFragmentChild2>
}
或者,将 foreach
循环与 Enumerable.Range 结合使用,而不是使用 for
循环:
@foreach (var c in Enumerable.Range(1, 3))
{
<RenderFragmentChild2 Id="@($"Child{c}")">
Count: @c
</RenderFragmentChild2>
}
捕获对组件的引用
组件引用提供了一种引用组件实例以便发出命令的方法。 若要捕获组件引用,请执行以下操作:
- 向子组件添加
@ref
特性。 - 定义与子组件类型相同的字段。
呈现组件时,将用组件实例填充字段。 然后,可以在实例上调用 .NET 方法。
请考虑以下 ReferenceChild
组件,它会在调用其 ChildMethod
时记录消息。
ReferenceChild.razor
:
@inject ILogger<ReferenceChild> Logger
@if (value > 0)
{
<p>
<code>value</code>: @value
</p>
}
@code {
private int value;
public void ChildMethod(int value)
{
Logger.LogInformation("Received {Value} in ChildMethod", value);
this.value = value;
StateHasChanged();
}
}
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> Logger
@code {
public void ChildMethod(int value)
{
Logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> Logger
@code {
public void ChildMethod(int value)
{
Logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> Logger
@code {
public void ChildMethod(int value)
{
Logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
@using Microsoft.Extensions.Logging
@inject ILogger<ReferenceChild> Logger
@code {
public void ChildMethod(int value)
{
Logger.LogInformation("Received {Value} in ChildMethod", value);
}
}
组件引用仅在呈现组件后才进行填充,其输出包含 ReferenceChild
的元素。 在呈现组件之前,没有任何可引用的内容。 请勿尝试直接将引用的组件方法调用事件处理程序(例如 @onclick="childComponent!.ChildMethod(5)"
),因为在分配单击事件时可能不会分配引用变量。
若要在组件完成呈现后操作组件引用,请使用 OnAfterRender
或 OnAfterRenderAsync
方法。
以下示例使用了前面的 ReferenceChild
组件。
ReferenceParent.razor
:
@page "/reference-parent"
<div>
<button @onclick="@(() => childComponent1!.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> (first instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent1" />
</div>
<div>
<button @onclick="CallChildMethod">
Call <code>ReferenceChild.ChildMethod</code> (second instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent2" />
</div>
@code {
private ReferenceChild? childComponent1;
private ReferenceChild? childComponent2;
private void CallChildMethod() => childComponent2!.ChildMethod(5);
}
@page "/reference-parent"
<div>
<button @onclick="@(() => childComponent1!.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> (first instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent1" />
</div>
<div>
<button @onclick="CallChildMethod">
Call <code>ReferenceChild.ChildMethod</code> (second instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent2" />
</div>
@code {
private ReferenceChild? childComponent1;
private ReferenceChild? childComponent2;
private void CallChildMethod()
{
childComponent2!.ChildMethod(5);
}
}
@page "/reference-parent"
<div>
<button @onclick="@(() => childComponent1!.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> (first instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent1" />
</div>
<div>
<button @onclick="CallChildMethod">
Call <code>ReferenceChild.ChildMethod</code> (second instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent2" />
</div>
@code {
private ReferenceChild? childComponent1;
private ReferenceChild? childComponent2;
private void CallChildMethod()
{
childComponent2!.ChildMethod(5);
}
}
@page "/reference-parent"
<div>
<button @onclick="@(() => childComponent1!.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> (first instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent1" />
</div>
<div>
<button @onclick="CallChildMethod">
Call <code>ReferenceChild.ChildMethod</code> (second instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent2" />
</div>
@code {
private ReferenceChild childComponent1;
private ReferenceChild childComponent2;
private void CallChildMethod()
{
childComponent2!.ChildMethod(5);
}
}
@page "/reference-parent"
<div>
<button @onclick="@(() => childComponent1!.ChildMethod(5))">
Call <code>ReferenceChild.ChildMethod</code> (first instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent1" />
</div>
<div>
<button @onclick="CallChildMethod">
Call <code>ReferenceChild.ChildMethod</code> (second instance)
with an argument of 5
</button>
<ReferenceChild @ref="childComponent2" />
</div>
@code {
private ReferenceChild childComponent1;
private ReferenceChild childComponent2;
private void CallChildMethod()
{
childComponent2!.ChildMethod(5);
}
}
尽管捕获组件引用使用与捕获元素引用类似的语法,但捕获组件引用不是 JavaScript 互操作功能。 组件引用不会传递给 JavaScript 代码。 组件引用只在 .NET 代码中使用。
重要事项
不要使用组件引用来改变子组件的状态。 请改用常规声明性组件参数将数据传递给子组件。 使用组件参数使子组件在正确的时间自动重新呈现。 有关详细信息,请参阅组件参数部分和 ASP.NET Core Blazor 数据绑定一文。
应用属性
可以通过 @attribute
指令将特性应用于组件。 下面的示例将 [Authorize]
特性应用于组件的类:
@page "/"
@attribute [Authorize]
条件 HTML 元素属性和 DOM 属性
Blazor 采用以下常规行为:
- 对于 HTML 属性,Blazor 根据 .NET 值有条件地设置或移除属性。 如果 .NET 值为
false
或null
,则不会设置或移除该属性(如果之前已设置该属性)。 - 对于 DOM 属性(例如
checked
或value
),Blazor 会基于 .NET 值设置 DOM 属性。 如果 .NET 值为false
或null
,则 DOM 属性将重置为默认值。
哪些 Razor 语法特性对应于 HTML 特性,哪些对应于 DOM 属性仍然没有记录,因为这是一个框架实现细节,可能会在不通知的情况下发生更改。
警告
某些 HTML 属性(例如 aria-pressed
)必须具有字符串值“true”或“false”。 由于它们需要字符串值而不是布尔值,因此必须使用 .NET string
而非使用 bool
作为其值。 这是由浏览器 DOM API 设置的要求。
原始 HTML
通常使用 DOM 文本节点呈现字符串,这意味着将忽略它们可能包含的任何标记,并将其视为文字文本。 若要呈现原始 HTML,请将 HTML 内容包装在 MarkupString 值中。 将该值分析为 HTML 或 SVG,并插入到 DOM 中。
警告
呈现从任何不受信任的源构造的原始 HTML 存在安全风险,应始终避免。
下面的示例演示如何使用 MarkupString 类型向组件的呈现输出添加静态 HTML 内容块。
MarkupStrings.razor
:
@page "/markup-strings"
<PageTitle>Markup Strings</PageTitle>
<h1>Markup Strings Example</h1>
@((MarkupString)myMarkup)
@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup string</em>.</p>";
}
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>";
}
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>";
}
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>";
}
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>";
}
Razor 模板
可以使用 Razor 模板语法定义呈现片段,从而定义 UI 片段。 Razor 模板使用以下格式:
@<{HTML tag}>...</{HTML tag}>
下面的示例演示如何在组件中指定 RenderFragment 和 RenderFragment<TValue> 值并直接呈现模板。 还可以将呈现片段作为参数传递给模板化组件。
RazorTemplate.razor
:
@page "/razor-template"
<PageTitle>Razor Template</PageTitle>
<h1>Razor Template Example</h1>
@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; }
}
}
@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; }
}
}
@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; }
}
}
@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; }
}
}
@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; }
}
}
以上代码的呈现输出:
<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>
<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>
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<Robot />
</foreignObject>
</svg>
@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 中。
请考虑以下组件标记:
<ul>
@foreach (var item in Items)
{
<li>
@item.Text
</li>
}
</ul>
前面的示例呈现以下不必要的空白:
- 在
@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 组件(根组件)是加载应用创建的任何组件层次结构的第一个组件。
在从 Blazor Web App 项目模板创建的应用中,调用服务器端 Program
文件中的 MapRazorComponents<TRootComponent>
所声明的类型参数将 App
组件 (App.razor
) 指定为默认根组件。 以下示例演示如何使用 App
组件作为根组件,这是从 Blazor 项目模板创建的应用的默认组件:
app.MapRazorComponents<App>();
注意
不支持让根组件具有交互性(例如 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" />
静态呈现的组件只能添加到应用。 之后无法移除或更新它们。
有关更多信息,请参见以下资源: