问题
I have a Blazor server-side app, trying to understand the structure. Inside the MainLayout.razor page, I see the tag @Body, and this is where each page's content is rendered.
I am wondering, is it possible to add additional render element to the mainLayout page? For example, a @Header section. And I would prefer to define this section inside each individual page as well.
In other words, for each page, besides the main content, it also needs to define Header, Footer, or whatever render element I define in the MainLayout. This way, I can have customize Header/Footer elements that are unique for each page.
Thanks for any help.
回答1:
MainLayout.razor
Note: The use of a method to update the render fragment fields I deliberately made private which then calls StateHasChanged()
. Other methods could easily be created to clear or set other fields.
@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);
}
}
On any page...
@page "/"
<h1>Hello, world!</h1>
<LayoutSetter>
<Header>
Hello
</Header>
<Footer>
Goodbye
</Footer>
</LayoutSetter>
回答2:
Here is a more Dynamic solution:
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;
}
The Setter
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;
}
}
}
Provide the service:
In my case Program.cs
builder.Services.AddScoped<ILayoutService, LayoutService>();
Example
@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++;
}
This also clears the values on navigation as I am using IDisposable
. The Setter does not have to be set (Null is ok). If more than one setter is used the latest takes precedence. I have not tested dynamically removing multiple setters on one page.
Here is a repo using WebAssembly 3.2.1
来源:https://stackoverflow.com/questions/64255443/blazor-page-elements-other-than-body