Como posso modificar o layout de uma página blazor?

Oct 22 2020

Estou tentando implementar breadcrumbs no Blazor. Não parece haver nenhum mecanismo para passar dados upstream para o nível de layout, então estou me perguntando como as pessoas estão gerenciando isso.

Esta é uma versão simplificada do meu layout:

<header>
    <Toolbar />
    <Breadcrumbs></Breadcrumbs>
</header>

<main class="mt-5">
     @Body
</main>

A ideia é que em qualquer página do Blazor, eu quero ser capaz de definir breadcrumbs. Mas minhas páginas não podem interagir com o layout ou o componente breadcrumb.

Coisas que eu considerei / tentei:

  • Envolvi meu layout em um CascadingParameter e implementei o parâmetro dentro do componente breadcrumb e minha página. Isso não funcionou porque, evidentemente, os valores em cascata não podem ser alterados em um componente filho.
  • Um serviço com escopo inteligente que pode ser implementado tanto na página quanto no componente breadcrumb. Mas não existe um escopo para o nível de "página atual", então estou preocupado com artefatos estranhos se houvesse várias guias do navegador abertas, etc. Ocorre-me enquanto escrevo isso que duas guias podem gerar dois circuitos, que é algo para explorar, pois isso seria viável.
  • Desista e resolva isso com a interoperabilidade JS . Esta é a opção nuclear. É factível, apenas coxo. :) Deve haver uma maneira melhor, onde menos vidas são perdidas.

Neste antigo problema do GitHub, Steve Sanderson responde a uma necessidade semelhante, mas sua solução alternativa é invasiva. Espero que haja algo melhor agora:https://github.com/dotnet/aspnetcore/issues/16009

Isso foi interessante, mas no final das contas não foi útil: Blazor: Parâmetro de acesso do layout

Respostas

2 BrianMacKay Oct 23 2020 at 01:51

Padrão de recipiente de estado

Acabei resolvendo isso usando o padrão State Container.

Se você não sabe o que é, estes links são úteis:

https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/ (última seção) https://www.youtube.com/watch?v=BB4lK2kfKf0&feature=youtu.be (mergulho mais profundo com opções adicionais)

Visão geral

Fiz um serviço com escopo apenas para lidar com nav. Ele é injetado no meu componente Navbar, onde é usado para gerenciar migalhas de pão. O serviço de navegação também possui um evento que atualiza a interface do usuário da barra de navegação sempre que a localização atual muda.

As opções de navegação podem ser configuradas página a página.

Para tornar as coisas mais fáceis para mim, também criei uma página base que herda do ComponentBase.

Simplifiquei um pouco meu código. No meu projeto real, estou gerenciando mais do que apenas migalhas de pão aqui.

MainLayout

Observe o componente Navbar no cabeçalho.

<header>
    <Navbar />
</header>

<main>
     @Body
</main>

Componente Navbar

Isso usa o componente NavState para construir nossa localização atual e lidar com a visibilidade. Este exemplo usa mdbootstrap 4. No bloco de código no final, nos inscrevemos no evento OnChange e o usamos para renderizar novamente o componente. Também implementamos Dispose para eliminar essa associação de evento, caso contrário, podemos ter vazamentos de memória.

@inject NavState Nav
@implements IDisposable

<div class="subnav clearfix @(Nav.Visible ? "" : "invisible")">
    @*BREADCRUMBS*@
    <div class="float-left">
        <ol class="breadcrumb">
            @foreach (var item in Nav.Breadcrumbs)
            {
                if (item.Active)
                {
                    <li class="breadcrumb-item active">@item.Text</li>
                }
                else
                {
                    <li class="breadcrumb-item"><a href="@item.Link">@item.Text</a></li>
                }
            }
        </ol>
    </div>
</div>


@code {

    protected override void OnInitialized()
    {
        Nav.OnChange += StateHasChanged; 
    }

    public void Dispose()
    {
        Nav.OnChange -= StateHasChanged;
    }
}

Serviço NavState

Injetado como um serviço com escopo definido. No Blazor do lado do servidor, os serviços com escopo existem durante a vida útil da conexão do Blazor, portanto, devemos ter o cuidado de redefinir isso quando uma nova página for carregada.

Também vale a pena notar: se você abrir várias guias, cada guia terá sua própria conexão, portanto, não há chance de corrupção devido ao usuário ter várias guias abertas.

public class NavState : IScopedService
    {
        public List<Breadcrumb> Breadcrumbs { get; set; } = new List<Breadcrumb>(); 
        public bool Visible { get; set; } = false;

        public event Action OnChange;

        public void SetVisible(bool isVisible)
        {
            Visible = isVisible;
            NotifyStateChanged();
        }

        public void Reset()
        {
            Breadcrumbs = new List<Breadcrumb>();
            Visible = false;

            NotifyStateChanged();
        }

        public void SetBreadcrumbs(List<Breadcrumb> breadcrumbs)
        {
            Breadcrumbs = breadcrumbs;
            Visible = true;

            NotifyStateChanged();
        }

        private void NotifyStateChanged() => OnChange?.Invoke();
    }
}

A localização atual é simples:

    public class Breadcrumb 
    {
        public string Text { get; set; }
        public string Link { get; set; }
        public bool Active { get; set; }

    }

Página de Base

    public class MyPageBase : ComponentBase
    {
        [Inject] protected NavState Nav { get; set; }

        protected override void OnInitialized()
        {
            // NavState (breadcrumbs, etc) is Scoped, so it lives as long as our connection lives. 
            // So when a new page is visited, we need to clear navigation to prevent breadcrumbs from bleed-over, etc. 
            // This also makes the navbar invisible by default.
            Nav.Reset();
        }
    }

Páginas

Com tudo isso fora do seu caminho, nas suas páginas, se você não fizer nada, a barra de navegação ficará invisível. Ou você pode adicionar breadcrumbs:

        protected override async Task OnInitializedAsync()
        {
            ...

            Nav.SetBreadcrumbs(new List<Breadcrumb>() 
            { new Breadcrumb(Text: "Test", Link: "https://google.com" }
            );
            
            ...
        }

Na minha implementação no mundo real, também criei um construtor fluente para tornar o trabalho com migalhas de pão um pouco menos complicado, mas não quero sobrecarregar as pessoas.