服务器端 Blazor 中的数据驱动布局





5.00/5 (2投票s)
服务器端 Blazor 中的数据驱动布局
在使用 Blazor 时,我偶然发现了一个问题,即如何创建一个根据页面上的数据而变化的布局。例如,您可能希望在页面的标题中包含面包屑导航。您可以从您的 MainLayout
中获取 URL 并尝试解析出应用程序中的位置,然后弄清楚创建面包屑导航所需的信息。但理想情况下,您应该让每个页面确定自己的面包屑导航,并将该数据传递给标题。
在 Razor Pages 中,您可以使用布局中的 RenderSection
,然后在页面中定义 Section
,它可以是一个组件,您可以向其传递一些值。但是,RenderSection
尚未在 Blazor 中实现(我不确定它是否会实现)。这里有两种替代技术。
首先,我们需要打开 *\_ViewImports.cshtml* 并注释掉或删除定义文件夹中所有页面布局的 @layout MainLayout
。
然后,我们将创建一个 AppState
类来保存我们想要在页面和布局之间传递的数据。现在,我们只捕获页面名称。这部分在两种技术中都是通用的。
public class AppState
{
public string CurrentPageName { get; set; }
}
这两种技术也共享 NavMenu
,它接受 AppState
并使用它来显示 CurrentPageName
。
@if (!string.IsNullOrEmpty(appState.CurrentPageName))
{
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
<ul class="nav flex-column">
<li class="nav-item px-3" style="color:white;">
Current Page: @appState.CurrentPageName
</li>
</ul>
</div>
}
@functions {
[Parameter] protected AppState appState { get; set; }
bool collapseNavMenu = true;
void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
布局组件
对于布局组件技术,我们将创建一个名为 MainLayout2
的 Blazor 组件,该组件从调用标记中获取其子内容。它有两个关键参数,AppState
和 ChildContent
。它只是将 AppState
传递给 NavMenu
组件。
@inherits BlazorLayoutComponent
<div class="sidebar">
<NavMenu appState="@appState" />
</div>
<div class="main">
<div class="top-row px-4">
<a href="https://blazor.net" target="_blank" class="ml-md-auto">About</a>
</div>
<div class="content px-4">
@ChildContent
</div>
</div>
@functions
{
[Parameter] protected AppState appState { get; set; }
[Parameter] protected RenderFragment ChildContent { get; set; }
}
请注意,必须将 RenderFragment
命名为 ChildContent
,Blazor 才能在以后正确获取内容。
然后,从页面中,我们将页面的内容包装在 <MainLayout>
标记中,并从 OnInit
中,将 AppState.CurrentPageName
设置为页面名称。
@page "/counter"
@inherits BlazorComponent
<MainLayout2 AppState="@appState">
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>
</MainLayout2>
@functions {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
protected AppState appState { get; set; } = new AppState();
protected override void OnInit()
{
appState.CurrentPageName = "Counter";
base.OnInit();
}
}
依赖注入
另一种可能性是使用依赖注入将 AppState
的单个实例注入到页面和布局中,然后页面中对该对象的更新将对布局可见。
首先,我们需要在 StartUp.ConfigureServices
中注册 AppState
类。我们需要将其注册为 Scoped 生命周期。有关 Blazor 中各种生命周期的更好理解,请参阅我关于 Blazor DI 生命周期 的文章。
services.AddScoped<AppState>();
然后在 *MainLayout.cshtml* 中,我们注入 AppState
并将其传递给 NavMenu
。
@inherits BlazorLayoutComponent
@inject AppState appState
<div class="sidebar">
<NavMenu appState="@appState" />
</div>
<div class="main">
<div class="top-row px-4">
<a href="https://blazor.net" target="_blank" class="ml-md-auto">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
我们还需要将 AppState
注入到页面中,并在 OnInit
方法中设置 CurrentPageName
。在这种情况下,由于我们已经从 *\_ViewImports.cshtml* 中删除了 @layout
指令,因此我们需要在页面中指定布局。
@page "/counter2"
@inject AppState appState
@layout MainLayout
<h1>Counter2</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" onclick="@IncrementCount">Click me</button>
@functions {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
protected override void OnInit()
{
appState.CurrentPageName = "Counter";
base.OnInit();
}
}
哪个更好
最大的区别是 AppState
的生命周期。使用布局组件技术,您每次页面加载时都会创建一个新的 AppState
实例。使用依赖注入技术,您可以保留相同的 AppState
,直到用户刷新。
这是一把双刃剑。一方面,如果您需要从数据库或 API 中检索一些数据,您已经内置了缓存。另一方面,您需要记住数据可能已经过时。
有关此示例,您可以下载并运行完整的项目,导航到 */Counter2*,然后导航到 FetchData
。您会看到当前页面仍然列为 Counter2
,因为设置 CurrentPageName
的行已被注释掉。
布局组件技术确实需要您将内容包装在布局标签中。这不仅在每个页面中都是额外的样板代码,而且可能使嵌套布局等操作更加困难。
完整的源代码可以在 https://github.com/hutchcodes/Blazor.DataDrivenLayout 中找到。