ASP.NET Core Blazor 布局

注意

此版本不是本文的最新版本。 有关当前版本,请参阅本文.NET 9 版本。

警告

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

重要

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

有关当前版本,请参阅本文.NET 9 版本。

本文介绍如何为 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

<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 提供。

应用布局

使布局命名空间可用

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>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 应用的类型来 identity 布局的命名空间。 有关详细信息,请参阅使布局命名空间可用部分。

重要

Blazor Web App 不使用 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

<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>
@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 部分

其他资源