Как передать значение со страницы в макет в Blazor?

#blazor #blazor-server-side #blazor-webassembly

#blazor #blazor-на стороне сервера #blazor-webassembly

Вопрос:

У меня есть layout ( MainLayout.razor ) , и у него есть вызываемый флаг ShowFooter . На некоторых страницах я хочу иметь возможность установить этот флаг true равным, а некоторые другие false — равными.

Мне не удалось найти никаких четких инструкций о том, как страница (то есть компонент с маршрутом) может взаимодействовать со своим макетом. Как это можно / должно быть сделано в Blazor?

Примечание: вы можете предложить иметь 2 макета, один с нижним колонтитулом, а другой без него, но это не решит мою проблему, я хочу иметь возможность показывать и скрывать нижний колонтитул в разное время на одной и той же странице. Кроме того, это всего лишь один сценарий, в котором необходимо обмениваться данными между макетом и страницей. Есть также бесчисленное множество других.

Ответ №1:

Самый простой способ сделать это — определить общедоступное логическое свойство с именем ShowFooter в компоненте MainLaout следующим образом:

 public bool ShowFooter {get; set;}
 

И каскадировать ссылку на компонент MainLaout для заданных компонентов, перенося разметку в CascadingValue компонент, для атрибута Value которого установлено значение this , например:

 @inherits LayoutComponentBase


<CascadingValue Value="this">
     <div class="sidebar">
        <NavMenu />
    </div>
    <div class="main">
         <div class="content px-4">
            @Body
        </div>
    </div>
</CascadingValue>
@code
{
    public bool ShowFooter {get; set;}

     protected override void OnInitialized()
    {
      // Put here code that checks the value of ShowFooter and acts in 
      // accordance with your dear wishes

     }
}
 

Использование в Index.razor

 @code{
     // Gets a reference to the MainLayout component
    [CascadingParameter]
    public MainLayout Layout { get; set; } 

    protected override void OnInitialized()
    {
        Layout.ShowFooter= true;
    
    }
}
 

Комментарии:

1. Примечание: шаблон AppState обрабатывает состояние компонентов в целом..

2. По-видимому, макет не отображается повторно, когда я изменяю общедоступные свойства в компоненте, даже когда я вызываю StateHasChanged() метод. Это правда?

3. Понял. Спасибо. Кстати, опять же, как я уже сказал, изменение общедоступных свойств layout в других компонентах, например, в событиях пользовательского интерфейса, похоже, не приводит к повторному отображению макета. Это ограничение этого подхода? Или я делаю что-то не так?

4. Нет «ограничения этого подхода». Будьте уверены, что даже Стив Андерсон предложил бы вам написать такой код. Вам нужно следующее: InvokeAsync(() => StateHasChanged()); Примечание: я собираюсь опубликовать в новом ответе простую программу, которая может быть вам полезна. Скопируйте и запустите код. Введите текст в текстовое поле, расположенное в компоненте Index, и вы увидите вводимый текст в заголовке основного описания. Скопируйте код, так как я собираюсь его скоро удалить.

5. Или вы могли бы оставить это для других, чтобы они также могли извлечь выгоду.

Ответ №2:

Есть несколько способов сделать это:

  1. Самое уродливое: если у вас есть два шаблона, вы можете просто выбрать шаблон, который хотите использовать, со следующим в верхней части страницы / компонента:

    @layout NoFooterLayoutName

  2. Используйте каскадное значение в шаблоне (то, что я бы рекомендовал для вашего scenerio):
 <CascadingValue Value="Footer">
    <Child />
</CascadingValue>
 

Пример скрипки:
https://blazorfiddle.com/s/05spcuyk

И документ: https://docs.microsoft.com/en-us/aspnet/core/blazor/components/cascading-values-and-parameters?view=aspnetcore-5.0

  1. Создайте государственную службу и добавьте ее в startup как область действия. Государственная служба с переменной bool нижнего колонтитула, а затем может быть введена в страницы / компоненты и используемая переменная:

В методе startup.cs ConfigureService:

 services.AddScoped<AppState>();
 

Создайте класс AppState.cs где-нибудь в вашем проекте (в идеале в папке Services):

 public class AppState 
{
   public bool ShowFooter { get; set; }
   public event Action StateChanged;
   private void NotifyStateChanged() => StateChanged?.Invoke();
}
 

Затем введите его в свою страницу / компоненты, чтобы вы могли изменить элемент ShowFooter, а в своем шаблоне вы можете создать обработчик событий (не уверен, если это необходимо) для того, чтобы вызвать StateHasChanged():

 @inject AppState _AppState;
@implements IDisposable
.
.
.
@code{
    protected override void OnInitialized()
    {
        _appState.StateChanged  = StateChanged;
    }

    public void StateChanged()
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        _appState.StateChanged -= StateChanged;
    }
}
 

Комментарии:

1. Спасибо. Мне это нравится. Но может AppState ли класс быть статическим классом, а не зарегистрированным как одноэлементный сервис? В чем преимущество использования его в качестве синглтона?

2. @Arad Класс AppState не должен быть статическим, внедрение зависимостей создаст экземпляр класса для каждого пользователя / запроса (область действия), классы состояний должны быть предназначены для хранения состояния пользователя — и это может быть не то, что вы хотите, чтобы это делало. Если вы правильно поняли свою проблему, лучшим вариантом может быть использование CascadingValue для отображения / скрытия нижнего колонтитула (bool) в компоненте макета (mainLayout), чтобы страницы могли установить для него все, что им может понравиться, в инициализированном методе переопределения….

3. @Arad … Имейте в виду, однако, что каждая страница должна была бы установить это, или у вас была бы ситуация, когда какая-то страница скроет нижний колонтитул, а затем при переходе на другую страницу, если нижний колонтитул не будет сброшен в true, он останется скрытым.

Ответ №3:

Вы могли бы использовать службу уведомлений и внедрить это в компоненты.

 public class NotifyService 
{
     public event Func<bool, Task> Notify;

     public async Task Notify(bool value) 
     {
        if (Notify is object)
        {
            await Notify.Invoke(value);
        }
     }
}
 

А затем зарегистрируйте это как одноэлементный (или ограниченный, если на стороне сервера) в контейнере DI и внедрите это в свои компоненты.