Body 之外的 Blazor 页面元素

问题描述 投票:0回答:3

我有一个 Blazor 服务器端应用程序,试图了解其结构。在 MainLayout.razor 页面中,我看到标签 @Body,这是渲染每个页面内容的地方。

我想知道,是否可以向 mainLayout 页面添加额外的渲染元素?例如,@Header 部分。我更愿意在每个单独的页面中定义此部分。

换句话说,对于每个页面,除了主要内容之外,还需要定义 Header、Footer 或我在 MainLayout 中定义的任何渲染元素。这样,我可以自定义每个页面唯一的页眉/页脚元素。

感谢您的帮助。

blazor blazor-server-side
3个回答
15
投票

MainLayout.razor

注意: 使用一种方法来更新渲染片段字段,我特意将其设为私有,然后调用

StateHasChanged()
。可以轻松创建其他方法来清除或设置其他字段。

@inherits LayoutComponentBase

<CascadingValue Value="this">
    <div class="page">
        <div class="sidebar">
            <NavMenu />
        </div>
        <div class="main">
            <div class="top-row px-4">
                @header
            </div>

            <div class="content px-4">
                @Body
                @footer
            </div>
        </div>
    </div>
</CascadingValue>

@code {
    private RenderFragment header;
    private RenderFragment footer;

    public void SetHeaderAndFooter(RenderFragment header, RenderFragment footer)
    {
        this.header = header;
        this.footer = footer;
        StateHasChanged();
    }
}

LayoutSetter.cs


public class LayoutSetter : ComponentBase
{
    [CascadingParameter]
    public MainLayout Layout { get; set; }

    [Parameter]
    public RenderFragment Header { get; set; }

    [Parameter]
    public RenderFragment Footer { get; set; }

    protected override void OnInitialized()
    {
        Layout.SetHeaderAndFooter(Header, Footer);
    }
}

在任何页面上...

@page "/"

<h1>Hello, world!</h1>

<LayoutSetter>
    <Header>
        Hello
    </Header>
    <Footer>
        Goodbye
    </Footer>
</LayoutSetter>

3
投票

这是一个更动态的解决方案:

MainLayout.razor

@inherits LayoutComponentBase

<div class="sidebar">
    <AppNavigation />
</div>

<div class="main">
    <div class="top-row px-4">
        <AppHeader />
    </div>

    <div class="content px-4">
        @Body
    </div>

    <div class="bottom-row px-4">
        <AppFooter />
    </div>
</div>

AppHeader.razor

@implements IDisposable

@LayoutService.Header
@code {
    [Inject]
    public ILayoutService LayoutService { get; set; }

    protected override void OnInitialized()
    {
        LayoutService.PropertyChanged += LayoutService_PropertyChanged;
        base.OnInitialized();
    }

    private void LayoutService_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(ILayoutService.Header))
        {
            StateHasChanged();
        }
    }

    public void Dispose()
    {
        if (LayoutService != null)
        {
            LayoutService.PropertyChanged -= LayoutService_PropertyChanged;
        }
    }

}

ILayoutService.cs

public interface ILayoutService
{    
    RenderFragment Header { get; }    
    SetHeader HeaderSetter { get; set; }
    event PropertyChangedEventHandler PropertyChanged;    
    void UpdateHeader();
}

LayoutService.cs

public class LayoutService : ILayoutService, INotifyPropertyChanged
{

    public RenderFragment Header => HeaderSetter?.ChildContent;

    public SetHeader HeaderSetter
    {
        get => headerSetter;
        set
        {
            if (headerSetter == value) return;
            headerSetter = value;
            UpdateHeader();
        }
    }

    public void UpdateHeader() => NotifyPropertyChanged(nameof(Header));

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private SetHeader headerSetter;
}

二传手

SetHeader.cs

public class SetHeader : ComponentBase, IDisposable
{
    [Inject]
    private ILayoutService Layout { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    protected override void OnInitialized()
    {
        if (Layout != null)
        {
            Layout.HeaderSetter = this;
        }
        base.OnInitialized();
    }

    protected override bool ShouldRender()
    {
        var shouldRender = base.ShouldRender();
        if (shouldRender)
        {
            Layout.UpdateHeader();
        }
        return shouldRender;
    }

    public void Dispose()
    {
        if (Layout != null)
        {
            Layout.HeaderSetter = null;
        }
    }

}

提供服务:

就我而言

Program.cs

 builder.Services.AddScoped<ILayoutService, LayoutService>();

示例

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

<SetHeader>
    <p>Hello Current count: @currentCount</p>
</SetHeader>

<SetFooter>
    <p>Goodbye Current count: @currentCount</p>
</SetFooter>

@code {
    private int currentCount = 0;
    private void IncrementCount() => currentCount++;
}

enter image description here

这也会清除导航上的值,因为我正在使用

IDisposable
。 Setter 不必设置(Null 也可以)。如果使用多个 setter,则最新的优先。我还没有测试过动态删除一页上的多个设置器。

这是一个使用 WebAssembly 3.2.1 的 repo


0
投票
此处给出的两个答案并不反映 Blazor 文档当前建议的方式,至少在 .NET8 中是这样。他们建议使用

sections。 如果我们想制作一张引导卡,请创建如下布局:

@using Microsoft.AspNetCore.Components.Sections @inherits LayoutComponentBase <div class="card" style="width: 18rem;"> <div class="card-header"> <SectionOutlet SectionName="header"/> </div> <div class="card-body"> @Body </div> <div class="card-footer"> <SectionOutlet SectionName="footer"/> </div>
然后我们就可以创建一个页面了:

<SectionContent SectionName="header"> <h3>An interesting title.</h3> </SectionContent> <p>This will be the body text of the card.</p> <SectionContent SectionName="footer"> <p>Some footer content</p> </SectionContent>
祝你好运!

© www.soinside.com 2019 - 2024. All rights reserved.