#blazor #blazor-server-side
#блейзор #блейзор- на стороне сервера
Вопрос:
У меня есть серверное приложение Blazor, которое пытается понять структуру. Внутри страницы mainLayout.razor я вижу тег @Body, и именно здесь отображается содержимое каждой страницы.
Мне интересно, можно ли добавить дополнительный элемент рендеринга на страницу основного описания? Например, раздел @Header. И я бы предпочел также определить этот раздел внутри каждой отдельной страницы.
Другими словами, для каждой страницы, помимо основного содержимого, также необходимо определить верхний, нижний колонтитул или любой другой элемент рендеринга, который я определяю в mainLayout. Таким образом, я могу настроить элементы верхнего и нижнего колонтитулов, которые уникальны для каждой страницы.
Спасибо за любую помощь.
Комментарии:
1. Я не знаю, можете ли вы на своей странице макета обнаружить события навигации: blazor-university.com/routing/detecting-navigation-events и измените макет в соответствии с вашими требованиями
Ответ №1:
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>
Комментарии:
1. Выглядит красиво. Это полностью нулевое доказательство?
2. @ХенкХолтерман github.com/BrianLParker/LayoutDemo
3. Большое спасибо, Брайан, я попробую.
4. Я сделал что-то подобное в этой библиотеке, тогда я был недостаточно умен, чтобы использовать фрагменты рендеринга 🙂 nuget.org/packages/CleanView
5. Я внедрил ваш код, Брайан, работает как шарм, еще раз спасибо.
Ответ №2:
Вот более динамичное решение:
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 ;
}
Это также очищает значения при навигации, которые я использую IDisposable
. Установщик не обязательно должен быть установлен (значение Null в порядке). Если используется более одного параметра настройки, последний имеет приоритет. Я не тестировал динамическое удаление нескольких сеттеров на одной странице.
Вот репозиторий с использованием WebAssembly 3.2.1