最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Updating global breadcrumbs in blazor - Stack Overflow

programmeradmin0浏览0评论

I created this BreadcrumbService so that I can use it to inject breadcrumbs and update them globally.

public class BreadcrumbService
{
    private List<BreadcrumbItem> _breadcrumbItems = new List<BreadcrumbItem>();

    public List<BreadcrumbItem> BreadcrumbItems
    {
        get => _breadcrumbItems;
        set
        {
            _breadcrumbItems = value;
            NotifyStateChanged();
        }
    }

    public event Func<Task>? OnBreadcrumbsChanged;

    public void SetBreadcrumbs(IEnumerable<BreadcrumbItem>? items)
    {
        _breadcrumbItems = items?.ToList() ?? new List<BreadcrumbItem>();
        NotifyStateChanged();
    }

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

And I have injected the breadcrumbs into the MainLayout as such:

<MudMainContent>
    <MudContainer MaxWidth="MaxWidth.Large" Style="height:max-content">
        <MudBreadcrumbs Items="BreadcrumbService.BreadcrumbItems"></MudBreadcrumbs>
        <MudPaper Elevation="3" Style="height:100dvh;">
            @Body
        </MudPaper>
    </MudContainer>
</MudMainContent>

And in AppLayout, I am initializing the breadcrumbs:

protected override async Task OnInitializedAsync()
{
    BreadcrumbService.OnBreadcrumbsChanged += BreadcrumbsService_OnChange;
}
 

public void Dispose()
{
    BreadcrumbService.OnBreadcrumbsChanged -= BreadcrumbsService_OnChange;
}

private async Task BreadcrumbsService_OnChange()
{
    Breadcrumbs = BreadcrumbService.BreadcrumbItems;
    await InvokeAsync(StateHasChanged);
}

And finally I am using it as such:

@inject BreadcrumbService BreadcrumbService
protected override async Task OnInitializedAsync()
{
    BreadcrumbService.SetBreadcrumbs(new List<BreadcrumbItem>
    {
        new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home)
    });

    StateHasChanged();
}
.
.
.
// In other component
BreadcrumbService.SetBreadcrumbs(new List<BreadcrumbItem>
{
    new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home),
    new BreadcrumbItem("Blogs", href: "/blogs"),
    new BreadcrumbItem($"Blog {Id}", href: $"/blogs/{ChapId}")
});

My problem is that, it takes two clicks to update the breadcrumbs, and their behaviour is non deterministic, could you help me to fix this bug so that it updates the breadcrumbs on initialized a component?

I created this BreadcrumbService so that I can use it to inject breadcrumbs and update them globally.

public class BreadcrumbService
{
    private List<BreadcrumbItem> _breadcrumbItems = new List<BreadcrumbItem>();

    public List<BreadcrumbItem> BreadcrumbItems
    {
        get => _breadcrumbItems;
        set
        {
            _breadcrumbItems = value;
            NotifyStateChanged();
        }
    }

    public event Func<Task>? OnBreadcrumbsChanged;

    public void SetBreadcrumbs(IEnumerable<BreadcrumbItem>? items)
    {
        _breadcrumbItems = items?.ToList() ?? new List<BreadcrumbItem>();
        NotifyStateChanged();
    }

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

And I have injected the breadcrumbs into the MainLayout as such:

<MudMainContent>
    <MudContainer MaxWidth="MaxWidth.Large" Style="height:max-content">
        <MudBreadcrumbs Items="BreadcrumbService.BreadcrumbItems"></MudBreadcrumbs>
        <MudPaper Elevation="3" Style="height:100dvh;">
            @Body
        </MudPaper>
    </MudContainer>
</MudMainContent>

And in AppLayout, I am initializing the breadcrumbs:

protected override async Task OnInitializedAsync()
{
    BreadcrumbService.OnBreadcrumbsChanged += BreadcrumbsService_OnChange;
}
 

public void Dispose()
{
    BreadcrumbService.OnBreadcrumbsChanged -= BreadcrumbsService_OnChange;
}

private async Task BreadcrumbsService_OnChange()
{
    Breadcrumbs = BreadcrumbService.BreadcrumbItems;
    await InvokeAsync(StateHasChanged);
}

And finally I am using it as such:

@inject BreadcrumbService BreadcrumbService
protected override async Task OnInitializedAsync()
{
    BreadcrumbService.SetBreadcrumbs(new List<BreadcrumbItem>
    {
        new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home)
    });

    StateHasChanged();
}
.
.
.
// In other component
BreadcrumbService.SetBreadcrumbs(new List<BreadcrumbItem>
{
    new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home),
    new BreadcrumbItem("Blogs", href: "/blogs"),
    new BreadcrumbItem($"Blog {Id}", href: $"/blogs/{ChapId}")
});

My problem is that, it takes two clicks to update the breadcrumbs, and their behaviour is non deterministic, could you help me to fix this bug so that it updates the breadcrumbs on initialized a component?

Share Improve this question asked Feb 3 at 21:37 SachihiroSachihiro 1,7934 gold badges27 silver badges56 bronze badges 3
  • Just use Blazor Sections much easier than all this. learn.microsoft/en-us/aspnet/core/blazor/components/… – Brian Parker Commented Feb 4 at 1:22
  • I accepted the answer from Shaun Curtis, but can you explain, how can sections be used in this context? – Sachihiro Commented Feb 4 at 7:10
  • Sure, see below. Shaun's answer is perfectly valid. Just an alternative way for the community. – Brian Parker Commented Feb 4 at 15:41
Add a comment  | 

2 Answers 2

Reset to default 0

Your problem is in the page update logic. You can simplify the code and logic like this.

BreadcrumbService

The only way to set the breadcrumbs is through the SetBreadcrumbs method.

public class BreadcrumbService
    {
        private List<BreadcrumbItem> _breadcrumbItems = new List<BreadcrumbItem>();

        public IReadOnlyList<BreadcrumbItem> BreadcrumbItems => _breadcrumbItems.AsReadOnly();

        public event EventHandler? OnBreadcrumbsChanged;

        public void SetBreadcrumbs(IEnumerable<BreadcrumbItem>? items, object? sender = null)
        {
            _breadcrumbItems = items?.ToList() ?? new List<BreadcrumbItem>();
            NotifyStateChanged(sender);
        }

        private void NotifyStateChanged(object? sender = null) => OnBreadcrumbsChanged?.Invoke(sender, EventArgs.Empty);
    }
}

BreadcrumbComponent.razor

Component to encapsulate the Breadcrumb and update on change.

@inject BreadcrumbService breadcrumbService
@implements IDisposable

<MudBreadcrumbs Items="breadcrumbService.BreadcrumbItems"></MudBreadcrumbs>

@code {
    protected override void OnInitialized()
    {
        breadcrumbService.OnBreadcrumbsChanged += BreadcrumbsService_OnChange;
    }

    private void BreadcrumbsService_OnChange(object? sender, EventArgs e)
    {
        if (sender != this)
            this.InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        breadcrumbService.OnBreadcrumbsChanged -= BreadcrumbsService_OnChange;
    }
}

MainLayout.razor

    <MudMainContent Class="mt-16 pa-4">
        <BreadcrumbComponent />
        @Body
    </MudMainContent>

And Home.razor

@page "/"
@inject BreadcrumbService BreadcrumbService

//...

@code
{
    protected override void OnInitialized()
    {
        BreadcrumbService.SetBreadcrumbs(new List<BreadcrumbItem>
        {
            new BreadcrumbItem("Home", href: "/", icon: Icons.Material.Filled.Home)
        });
    }
}

Make use of the built-in components called SectionOutlet SectionContent.

Somewhere in your app where you want your breadcrumbs displayed.

Mainlayout.razor

@using Microsoft.AspNetCore.Components.Sections
...
<div class="top-row px-4">            
    <SectionOutlet SectionName="Breadcrumbs" /> <a href="https://learn.microsoft/aspnet/core/" target="_blank">About</a>
</div>

Breadcrumbs.razor

@using Microsoft.AspNetCore.Components.Sections
<SectionContent SectionName="Breadcrumbs">
    @ChildContent
</SectionContent>
@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

On any page:

<Breadcrumbs>
    <NavLink class="nav-link" href="" >
        <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
    </NavLink>
    Item2
</Breadcrumbs>

Of course, Breadcrumbs.razor does not have to capture the render fragment. You could use a parameter and build a display.

I like this way as you can still make use of the razor syntax to construct custom breadcrumbs for a particular page. It makes it clearer and easier to understand when reading the page or subcomponent for the page.

发布评论

评论列表(0)

  1. 暂无评论