Elementos da página do Blazor diferentes do corpo
Eu tenho um aplicativo do lado do servidor Blazor, tentando entender a estrutura. Dentro da página MainLayout.razor, vejo a tag @Body, e é aqui que o conteúdo de cada página é renderizado.
Eu estou me perguntando, é possível adicionar elemento de renderização adicional à página mainLayout? Por exemplo, uma seção @Header. E eu preferiria definir esta seção dentro de cada página individual também.
Ou seja, para cada página, além do conteúdo principal, ela também precisa definir Header, Footer ou qualquer elemento de renderização que eu definir no MainLayout. Dessa forma, posso personalizar elementos de cabeçalho / rodapé que são exclusivos para cada página.
Obrigado por qualquer ajuda.
Respostas
MainLayout.razor
Nota: O uso de um método para atualizar os campos de fragmento de renderização que eu deliberadamente tornei privado que então chama StateHasChanged(). Outros métodos podem ser facilmente criados para limpar ou definir outros campos.
@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);
}
}
Em qualquer página ...
@page "/"
<h1>Hello, world!</h1>
<LayoutSetter>
<Header>
Hello
</Header>
<Footer>
Goodbye
</Footer>
</LayoutSetter>
Aqui está uma solução mais dinâmica:
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;
}
O 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;
}
}
}
Forneça o serviço:
No meu caso Program.cs
builder.Services.AddScoped<ILayoutService, LayoutService>();
Exemplo
@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++;
}
Isso também limpa os valores da navegação conforme estou usando IDisposable. O Setter não precisa ser definido (nulo está ok). Se mais de um setter for usado, o último tem precedência. Não testei a remoção dinâmica de vários configuradores em uma página.
Aqui está um repo usando WebAssembly 3.2.1