ASP.NET Core Razor 组件

说明

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

本文介绍如何在 Blazor 应用中创建和使用 Razor 组件,包括有关 Razor 语法、组件命名、命名空间和组件参数的指导。

Razor 组件

Blazor 应用是使用 Razor 组件(非正式地称为 Blazor 组件组件)构建的。 组件是用户界面 (UI) 的自包含部分,具有用于启用动态行为的处理逻辑。 组件可以嵌套、重用、在项目间共享,并可在 MVC 和 Razor Pages 应用中使用。

组件呈现为浏览器文档对象模型 (DOM) 的内存中表现形式,它被称为“呈现树”,用于以灵活高效的方式更新 UI

尽管“Razor 组件”与其他 ASP.NET Core 内容呈现技术共享某种命名,但 Razor 组件必须与 ASP.NET Core 中的以下不同功能区分开来:

重要

使用 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 组件。 有关详细信息(包括对 NavLinkNavMenu 组件的描述),请参阅 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 组件的 TitleBody 组件参数通过自变量在呈现组件实例的 HTML 标记中进行设置。 以下 ParameterParent 组件会呈现两个 ParameterChild 组件:

  • 第一个 ParameterChild 组件在呈现时不提供参数自变量。
  • 第二个 ParameterChild 组件从 ParameterParent 组件接收 TitleBody 的值,后者使用显式 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 中的参数

应将组件参数声明为自动属性,这意味着它们不应在其 getset 访问器中包含自定义逻辑。 例如,下面的 StartData 属性是自动属性:

[Parameter]
public DateTime StartData { get; set; }

不要在 getset 访问器中放置自定义逻辑,因为组件参数专门用作父组件向子组件传送信息的通道。 如果子组件属性的 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 访问器。 组件通常使用反射实例化和分配参数值,从而绕过了 initrequired 本应做出的保证。 请改为使用 [EditorRequired] 特性以指定所需的组件参数。

不要对组件参数属性使用 init 访问器,因为设置具有 ParameterView.SetParameterProperties 的组件参数值会使用反射,这会绕过仅初始化的资源库限制。 应用 [EditorRequired] 特性以指定所需的组件参数。

不要对组件参数属性使用 init 访问器,因为设置具有 ParameterView.SetParameterProperties 的组件参数值会使用反射,这会绕过仅初始化的资源库限制。

组件参数和 RenderFragment 类型支持 TuplesAPI 文档)。 下面的组件参数示例在 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);
}

引用 ©2005 环球影业冲出宁静号内森·菲利安

组件参数和 RenderFragment 类型支持 TuplesAPI 文档)。 下面的组件参数示例在 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);
}

引用 ©2005 环球影业冲出宁静号内森·菲利安

路由参数

组件可以在 @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 段表示为 RenderFragmentChildContent 在组件 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; }
}

重要事项

必须按约定将接收 RenderFragment 内容的属性命名为 ChildContent

RenderFragment 不支持事件回叫

以下组件通过将内容置于子组件的开始标记和结束标记内,来提供用于呈现 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)"),因为在分配单击事件时可能不会分配引用变量。

若要在组件完成呈现后操作组件引用,请使用 OnAfterRenderOnAfterRenderAsync 方法

以下示例使用了前面的 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 值为 falsenull,则不会设置或移除该属性(如果之前已设置该属性)。
  • 对于 DOM 属性(例如 checkedvalue),Blazor 会基于 .NET 值设置 DOM 属性。 如果 .NET 值为 falsenull,则 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}>

下面的示例演示如何在组件中指定 RenderFragmentRenderFragment<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 组件,其中 idapp

<div id="app">...</app>

MVC 和 Razor Pages 应用还可以使用组件标记帮助程序来注册静态呈现的 Blazor WebAssembly 根组件:

<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

静态呈现的组件只能添加到应用。 之后无法移除或更新它们。

有关更多信息,请参见以下资源: