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 rendere
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;
}
}
}
In my case 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++;
}
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
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>