注意
此版本不是本文的最新版本。 有关当前版本的信息,请参阅.NET 9 版本的本文。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本的信息,请参阅.NET 9 版本的本文。
本文介绍如何为 Blazor 应用创建可重用布局组件。
Blazor 布局的有用性
有些应用元素(例如菜单、版权消息和公司徽标)通常是应用整体布局的一部分。 将这些元素的标记副本放入应用的所有组件是一种效率较低的做法。 每次更新其中一个元素时,都必须同时更新使用该元素的每个组件。 这种方法的维护成本很高,并且如果缺少更新,还可能会导致内容不一致。 布局可以解决这些问题。
Blazor 布局是一个 Razor 组件,它与引用该布局的组件共享标记。 布局可以使用数据绑定、依赖关系注入和组件的其他功能。
创建布局组件
要创建布局组件:
- 创建由 Razor 模板或 C# 代码定义的 Razor 组件。 基于 Razor 模板的布局组件像普通
.razor
组件一样使用 Razor 文件扩展名。 由于布局组件是在应用组件间共享的,因此它们通常放置在应用的Shared
或Layout
文件夹中。 但是,布局可以放置在使用它的组件可访问的任何位置。 例如,可以将布局放在使用它的组件所在的同一文件夹中。 - 组件继承自 LayoutComponentBase。 LayoutComponentBase 为布局内呈现的内容定义 Body 属性(RenderFragment 类型)。
- 使用 Razor 语法
@Body
在布局标记中指定呈现内容的位置。
注意
有关 RenderFragment 的详细信息,请参阅 ASP.NET Core Razor 组件。
以下 DoctorWhoLayout
组件显示布局组件的 Razor 模板。 布局继承 LayoutComponentBase 并在导航栏 (@Body
) 和页脚 (<nav>...</nav>
) 之间设置 <footer>...</footer>
。
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
<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(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
提供。
静态呈现的布局组件
Blazor Web App当采用每页/组件呈现(Routes
组件未指定交互式呈现模式)时,布局组件将以静态方式呈现在服务器上。 不支持将交互式呈现模式直接应用于布局,因为Blazor不支持将 (RenderFragment在本例中)序列化@Body
为根组件参数。 例如,将 @rendermode InteractiveServer
放置在 MainLayout
组件的顶部会导致以下运行时异常:
System.InvalidOperationException:无法使用 rendermode“InteractiveServerRenderMode”将参数“Body”传递给组件“MainLayout”。 这是因为该参数属于委托类型“Microsoft.AspNetCore.Components.RenderFragment”,这是任意代码,而且无法序列化。
这适用于在采用每页/组件呈现的应用中,从 LayoutComponentBase 继承的任何布局组件。
此方案可能在将来的 Blazor版本中得到解决。 有关详细信息,请参阅 [Blazor] 支持从 SSR 序列化呈现片段(dotnet/aspnetcore
#52768)。 同时,在每页/组件呈现模式下,您可以采取以下方法 Blazor Web App。
创建能够交互的包装组件。 在下面的示例中,包装组件包含一个可以从子组件接收内容的Blazor节。
在 _Imports.razor
文件中,为节添加指令 @using
(Microsoft.AspNetCore.Components.Sections):
@using Microsoft.AspNetCore.Components.Sections
在文件夹中创建以下交互式包装组件 Pages
。
Pages/InteractiveWrapper.razor
:
@rendermode InteractiveServer
<div>
<SectionOutlet SectionName="top-bar" />
</div>
@ChildContent
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
该 Counter
组件可以使用包装组件并设置交互式节内容。 在以下示例中,计数器按钮放置在该节中。
Pages/Counter.razor
:
@page "/counter"
@rendermode InteractiveServer
<InteractiveWrapper>
<SectionContent SectionName="top-bar">
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</SectionContent>
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</InteractiveWrapper>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
应用程序附近的其他组件也可以在 InteractiveWrapper
组件中包装内容,并设置交互式部分的内容。
应用布局
使布局命名空间可用
Blazor 框架的布局文件位置和命名空间随时间而变。 根据 Blazor 的版本和你要构建的 Blazor 应用的类型,你可能需要在使用它时指明布局的命名空间。 在引用布局实现时,如果没有指明布局的命名空间就找不到布局,请采用以下任一方法:
向
@using
文件添加_Imports.razor
指令,用于指定布局的位置。 在以下示例中,名称为Layout
的布局文件夹位于Components
文件夹中,应用的命名空间为BlazorSample
:@using BlazorSample.Components.Layout
在使用此布局的组件定义顶部添加
@using
指令:@using BlazorSample.Components.Layout @layout DoctorWhoLayout
完全限定使用布局的命名空间:
@layout BlazorSample.Components.Layout.DoctorWhoLayout
向组件应用布局
使用 @layout
Razor 指令将布局应用于具有 Razor 指令的可路由 @page
组件。 编译器将 @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>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 标记由前面的 DoctorWhoLayout
和 Episodes
组件生成。 此处不会出现无关标记,以使读者能够专注于这两个相关组件提供的内容:
- 标头(
<h1>...</h1>
)中的 H1“数据库”标题(<header>...</header>
)、导航栏(<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>
直接在组件中指定布局会覆盖默认布局:
- 由从
@layout
文件导入的_Imports.razor
指令设置,如下文向组件文件夹应用布局部分所述。 - 设置为应用的默认布局,详见本文后面的将默认布局应用于应用部分所述。
向组件文件夹应用布局
应用的每个文件夹都可以选择包含名为 _Imports.razor
的模板文件。 编译器将导入文件中指定的指令包括在同一文件夹中的所有 Razor 模板内,并在其所有子文件夹中以递归方式包括。 因此,包含 _Imports.razor
的 @layout DoctorWhoLayout
文件可确保文件夹中的所有组件都使用 DoctorWhoLayout
组件。 无需将 @layout DoctorWhoLayout
重复添加到文件夹和子文件夹内的所有 Razor 组件 (.razor
)。
_Imports.razor
:
@layout DoctorWhoLayout
...
_Imports.razor
文件类似于用于 Razor 视图和页面的 _ViewImports.cshtml 文件,但专门应用于 Razor 组件文件。
在 _Imports.razor
中指定布局会覆盖指定为路由器的默认应用布局的布局,如下一部分所述。
警告
请勿在根 文件中添加 Razor@layout
指令,这会导致布局陷入无限循环。 请在 Router 组件中指定布局,以控制默认应用布局。 有关更多信息,请参阅下文将默认布局应用于应用部分。
使用 _Imports.razor
文件将布局应用于具有 @layout
指令的组件的文件夹,并且布局组件本身位于 _Imports.razor
文件的同一文件夹或文件夹层次结构中时,条件相同。 应用布局的无限循环,因为 @layout
指令也应用于布局组件。 为了避免递归问题,我们建议将布局组件存储在其自己的文件夹中(例如,Layouts
),远离 _Imports.razor
文件应用它们的位置。
将默认布局应用于应用
在 Router 组件的 RouteView 组件中指定默认应用布局。 使用 DefaultLayout 参数设置布局类型:
<RouteView RouteData="routeData" DefaultLayout="typeof({LAYOUT})" />
在前面的示例中,{LAYOUT}
占位符是布局(例如,如果布局文件名是 DoctorWhoLayout
,那么该占位符就是 DoctorWhoLayout.razor
)。 可能需要根据 .NET 版本和应用类型确定布局的 Blazor 命名空间。 有关详细信息,请参阅使布局命名空间可用部分。
在 Router 组件的 RouteView 中将布局指定为默认布局是一种实用的做法,因为你可以为每个组件或每个文件夹替代布局,如本文前面几个部分所述。 建议使用 Router 组件设置应用的默认布局,因为它是使用布局的最通用且灵活的方法。
将布局应用于任意内容(LayoutView
组件)
若要为任意 Razor 模板内容设置布局,请使用 LayoutView 组件指定布局。 您可以在任何 LayoutView 组件中使用 Razor。 下面的示例为 ErrorLayout
组件的 MainLayout
模板 (NotFound) 设置了一个名为 <NotFound>...</NotFound>
的布局组件。
<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 App 不使用 NotFound 参数(<NotFound>...</NotFound>
标记),但该参数受到支持以实现向后兼容,从而避免在框架中发生中断性变更。 服务器端 ASP.NET 核心中间件管道处理服务器上的请求。 使用服务器端技术来处理错误的请求。 有关详细信息,请参阅 ASP.NET Core Blazor 呈现模式。
注意
对于 .NET 5.0.1 和任何其他 5.x 版本,Router
组件包含的参数 PreferExactMatches
被设置为 @true
。 有关详细信息,请参阅 从 ASP.NET Core 3.1 迁移到 .NET 5。
嵌套布局
组件可以引用一个布局,该布局又可以引用另一个布局。 例如,嵌套布局可用于创建多级菜单结构。
以下示例演示如何使用嵌套布局。
Episodes
部分中显示的 组件是要显示的组件。 该组件引用 DoctorWhoLayout
组件。
以下 DoctorWhoLayout
组件是前文所示示例的修改版本。 标头和页脚元素已经删除,并且布局引用了另一个布局 ProductionsLayout
。
Episodes
组件在 @Body
中 DoctorWhoLayout
出现的位置被渲染。
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
<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>
) 元素。 带有DoctorWhoLayout
组件的 Episodes
会在@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>
@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 组件与 MVC 或 Razor Pages 集成。
章节
若要从 Razor 子组件控制布局内容,请参阅 ASP.NET Core Blazor 部分。