ASP.NET Core Blazor 布局

注意

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

重要

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

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

本文介绍如何为 Blazor 应用创建可重用布局组件。

Blazor 布局的有用性

有些应用元素(例如菜单、版权消息和公司徽标)通常是应用整体布局的一部分。 将这些元素的标记副本放入应用的所有组件是一种效率较低的做法。 每次更新其中一个元素时,都必须同时更新使用该元素的每个组件。 这种方法的维护成本很高,并且如果缺少更新,还可能会导致内容不一致。 “布局”可以解决这些问题。

Blazor 布局是一个 Razor 组件,它与引用它的组件共享标记。 布局可以使用数据绑定依赖关系注入和组件的其他功能。

布局组件

创建布局组件

要创建布局组件:

  • 创建由 Razor 模板或 C# 代码定义的 Razor 组件。 基于 Razor 模板的布局组件像普通 Razor 组件一样使用 .razor 文件扩展名。 由于布局组件是在应用组件间共享的,因此它们通常放置在应用的 SharedLayout 文件夹中。 但是,布局可以放置在使用它的组件可访问的任何位置。 例如,可以将布局放在使用它的组件所在的同一文件夹中。
  • 组件继承自 LayoutComponentBaseLayoutComponentBase 为布局内呈现的内容定义 Body 属性(RenderFragment 类型)。
  • 使用 Razor 语法 @Body 在布局标记中指定呈现内容的位置。

注意

有关 RenderFragment 的详细信息,请参阅 ASP.NET Core Razor 组件

以下 DoctorWhoLayout 组件显示布局组件的 Razor 模板。 布局继承 LayoutComponentBase 并在导航栏 (<nav>...</nav>) 和页脚 (<footer>...</footer>) 之间设置 @Body

DoctorWhoLayout.razor

@inherits LayoutComponentBase

<PageTitle>Doctor Who® Database</PageTitle>

<header>
    <h1>Doctor Who® Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } =
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/ https://www.bbc.com";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase

<header>
    <h1>Doctor Who™ Episode Database</h1>
</header>

<nav>
    <a href="main-list">Main Episode List</a>
    <a href="search">Search</a>
    <a href="new">Add Episode</a>
</nav>

@Body

<footer>
    @TrademarkMessage
</footer>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}

MainLayout 组件

在从 Blazor 项目模板创建的应用中,MainLayout 组件就是应用的默认布局。 Blazor 的布局采用 Flexbox layout model (MDN documentation)W3C 规范)。

Blazor 的 CSS 隔离功能将独立 CSS 样式应用于 MainLayout 组件。 按照惯例,样式由相同名称的随附样式表 MainLayout.razor.css 提供。 ASP.NET Core 框架的样式表实现可以在 ASP.NET Core 参考源(dotnet/aspnetcore GitHub 存储库)中查阅:

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

Blazor 的 CSS 隔离功能将独立 CSS 样式应用于 MainLayout 组件。 按照惯例,样式由相同名称的随附样式表 MainLayout.razor.css 提供。 ASP.NET Core 框架的样式表实现可以在 ASP.NET Core 参考源(dotnet/aspnetcore GitHub 存储库)中查阅:

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

应用布局

使布局命名空间可用

Blazor 框架的布局文件位置和命名空间随时间而变。 根据 Blazor 的版本和你要构建的 Blazor 应用的类型,你可能需要在使用它时指明布局的命名空间。 在引用布局实现时,如果没有指明布局的命名空间就找不到布局,请采用以下任一方法:

  • _Imports.razor 文件添加 @using 指令,用于指定布局的位置。 在以下示例中,名称为 Layout 的布局文件夹位于 Components 文件夹中,应用的命名空间为 BlazorSample

    @using BlazorSample.Components.Layout
    
  • 在使用布局的组件定义顶部添加 @using 指令:

    @using BlazorSample.Components.Layout
    @layout DoctorWhoLayout
    
  • 完全限定使用布局的命名空间:

    @layout BlazorSample.Components.Layout.DoctorWhoLayout
    

向组件应用布局

使用 @layoutRazor 指令将布局应用于具有 @page 指令的可路由 Razor 组件。 编译器将 @layout 转换为 LayoutAttribute,并将特性应用于组件类。

以下 Episodes 组件的内容插入到 DoctorWhoLayout 中的 @Body 位置:

Episodes.razor

@page "/episodes"
@layout DoctorWhoLayout

<h2>Doctor Who® Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sunmakers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>
@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>
@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>
@page "/episodes"
@layout DoctorWhoLayout

<h2>Episodes</h2>

<ul>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfknq">
            <em>The Ribos Operation</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vfdsb">
            <em>The Sun Makers</em>
        </a>
    </li>
    <li>
        <a href="https://www.bbc.co.uk/programmes/p00vhc26">
            <em>Nightmare of Eden</em>
        </a>
    </li>
</ul>

以下呈现的 HTML 标记由前面的 DoctorWhoLayoutEpisodes 组件生成。 此处不会出现无关标记,以使读者能够专注于这两个相关组件提供的内容:

  • 标头(<header>...</header>)中的 H1“数据库”标题(<h1>...</h1>)、导航栏(<nav>...</nav>),以及页脚(<footer>...</footer>)中的商标信息来自 DoctorWhoLayout 组件。
  • H2“剧集”标题(<h2>...</h2>)和剧集列表(<ul>...</ul>)来自 Episodes 组件。
<header>
    <h1 ...>...</h1>
</header>

<nav>
    ...
</nav>

<h2>...</h2>

<ul>
    <li>...</li>
    <li>...</li>
    <li>...</li>
</ul>

<footer>
    ...
</footer>

直接在组件中指定布局会覆盖默认布局

向组件文件夹应用布局

应用的每个文件夹都可以选择包含名为 _Imports.razor 的模板文件。 编译器将导入文件中指定的指令包括在同一文件夹中的所有 Razor 模板内,并在其所有子文件夹中以递归方式包括。 因此,包含 @layout DoctorWhoLayout_Imports.razor 文件可确保文件夹中的所有组件都使用 DoctorWhoLayout 组件。 无需将 @layout DoctorWhoLayout 重复添加到文件夹和子文件夹内的所有 Razor 组件 (.razor)。

_Imports.razor

@layout DoctorWhoLayout
...

_Imports.razor 文件类似于 Razor 视图和页面的 _ViewImports.cshtml 文件,但专门应用于 Razor 组件文件。

_Imports.razor 中指定布局会覆盖指定为路由器的默认应用布局的布局,如下一部分所述。

警告

请勿向根 _Imports.razor 文件添加 Razor@layout 指令,这会导致布局形成无限循环。 请在 Router 组件中指定布局,以控制默认应用布局。 有关更多信息,请参阅下文将默认布局应用于应用部分。

注意

@layoutRazor 指令只对具有 @page 指令的可路由 Razor 组件应用布局。

将默认布局应用于应用

Router 组件的 RouteView 组件中指定默认应用布局。 使用 DefaultLayout 参数设置布局类型:

<RouteView RouteData="routeData" DefaultLayout="typeof({LAYOUT})" />

在前面的示例中,{LAYOUT} 占位符是布局(例如,如果布局文件名为 DoctorWhoLayout.razor,则它为 DoctorWhoLayout)。 可能需要根据 .NET 版本和 Blazor 应用的类型来确定布局的命名空间。 有关详细信息,请参阅使布局命名空间可用部分。

Router 组件的 RouteView 中将布局指定为默认布局是一种实用的做法,因为你可以为每个组件或每个文件夹替代布局,如本文前面几个部分所述。 建议使用 Router 组件设置应用的默认布局,因为它是使用布局的最通用且灵活的方法。

将布局应用于任意内容(LayoutView 组件)

若要为任意 Razor 模板内容设置布局,请使用 LayoutView 组件指定布局。 您可以在任何 Razor 组件中使用 LayoutView。 下面的示例为 MainLayout 组件的 NotFound 模板 (<NotFound>...</NotFound>) 设置了一个名为 ErrorLayout 的布局组件。

<Router ...>
    <Found ...>
        ...
    </Found>
    <NotFound>
        <LayoutView Layout="typeof(ErrorLayout)">
            <h1>Page not found</h1>
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

可能需要根据 .NET 版本和 Blazor 应用的类型来确定布局的命名空间。 有关详细信息,请参阅使布局命名空间可用部分。

重要

Blazor Web 应用不使用 NotFound 参数(<NotFound>...</NotFound> 标记),但该参数支持向后兼容,以避免在框架中发生中断性变更。 服务器端 ASP.NET 核心中间件管道处理服务器上的请求。 使用服务器端技术来处理错误的请求。 有关详细信息,请参阅 ASP.NET Core Blazor 呈现模式

注意

随着 ASP.NET Core 5.0.1 的发布及任何附加 5.x 版本的推出,Router 组件包含 PreferExactMatches 参数(设置为 @true)。 有关详细信息,请参阅从 ASP.NET Core 3.1 迁移到 5.0

嵌套布局

组件可以引用一个布局,该布局又可以引用另一个布局。 例如,嵌套布局可用于创建多级菜单结构。

以下示例演示如何使用嵌套布局。 向组件应用布局部分中显示的 Episodes 组件是要显示的组件。 该组件引用 DoctorWhoLayout 组件。

以下 DoctorWhoLayout 组件是前文所示示例的修改版本。 标头和页脚元素已经删除,并且布局引用了另一个布局 ProductionsLayout。 在 DoctorWhoLayout 中出现 @Body 的位置,呈现 Episodes 组件。

DoctorWhoLayout.razor

@inherits LayoutComponentBase
@layout ProductionsLayout

<PageTitle>Doctor Who® Database</PageTitle>

<h1>Doctor Who® Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } =
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/ https://www.bbc.com";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}
@inherits LayoutComponentBase
@layout ProductionsLayout

<h1>Doctor Who™ Episode Database</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
    @TrademarkMessage
</div>

@code {
    public string TrademarkMessage { get; set; } = 
        "Doctor Who is a registered trademark of the BBC. " +
        "https://www.doctorwho.tv/";
}

ProductionsLayout 组件包含顶级布局元素,其中包含有标头 (<header>...</header>) 和页脚 (<footer>...</footer>) 元素。 具有 Episodes 组件的 DoctorWhoLayout 会在出现 @Body 的位置呈现。

ProductionsLayout.razor

@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>
@inherits LayoutComponentBase

<header>
    <h1>Productions</h1>
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

@Body

<footer>
    Footer of Productions Layout
</footer>

以下呈现的 HTML 标记由前面的嵌套布局生成。 此处不会出现无关标记,以使读者能够专注于这三个相关组件提供的嵌套内容:

  • 标头 (<header>...</header>)、生产导航栏 (<nav>...</nav>) 和页脚 (<footer>...</footer>) 元素以及它们的内容来自于 ProductionsLayout 组件。
  • H1“数据库”标题(<h1>...</h1>)、剧集导航栏(<nav>...</nav>)和商标信息(<div>...</div>)来自 DoctorWhoLayout 组件。
  • H2“剧集”标题(<h2>...</h2>)和剧集列表(<ul>...</ul>)来自 Episodes 组件。
<header>
    ...
</header>

<nav>
    <a href="main-production-list">Main Production List</a>
    <a href="production-search">Search</a>
    <a href="new-production">Add Production</a>
</nav>

<h1>...</h1>

<nav>
    <a href="main-episode-list">Main Episode List</a>
    <a href="episode-search">Search</a>
    <a href="new-episode">Add Episode</a>
</nav>

<h2>...</h2>

<ul>
    <li>...</li>
    <li>...</li>
    <li>...</li>
</ul>

<div>
    ...
</div>

<footer>
    ...
</footer>

与集成组件共享 Razor Pages 布局

当可路由组件集成到 Razor Pages 应用中时,该应用的共享布局可与这些组件配合使用。 有关详细信息,请参阅将 ASP.NET Core Razor 组件集成到 ASP.NET Core 应用中

当可路由组件集成到 Razor Pages 应用中时,该应用的共享布局可与这些组件配合使用。 有关详细信息,请参阅预呈现和集成 ASP.NET Core Razor 组件

章节

若要从 Razor 子组件控制布局内容,请参阅 ASP.NET Core Blazor 部分

其他资源