65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2018年9月25日

CPOL

3分钟阅读

viewsIcon

6943

服务器端 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 组件,该组件从调用标记中获取其子内容。它有两个关键参数,AppStateChildContent。它只是将 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 中找到。

© . All rights reserved.