Some PRO module pages don't display the correct breadcrumb for the menu they belong to in Blazor (MudBlazor).
For example, on the Language Management > Languages page, the breadcrumb only shows "Languages" instead of "Administration > Language Management > Languages".
Current behavior: Languages Expected behavior: Administration > Language Management > Languages
We noticed this behavior on the following pages:
- Language Management
- Languages
- Language Texts
- AI Management
- Workspaces
- MCP Servers
- Text Templates
- Audit Logs
Additionally, in AI Management:
- Chat Playground: the breadcrumb currently shows "Chat Playground - WorkspaceName". We would expect something like "Administration > AI Management > Workspaces > WorkspaceName > Playground", reflecting the navigation hierarchy.
- Data Sources: the page has no breadcrumb at all.
4 Answer(s)
-
0
In ABP Blazor (MudBlazor), breadcrumbs are not automatically derived from the menu hierarchy. They are rendered from
PageLayout.BreadcrumbItems/<PageHeader BreadcrumbItems=... />.
So if a PRO module page only sets the current page title (or sets breadcrumbs late / inconsistently), you will see onlyLanguages(or even nothing), instead ofAdministration > ....Fix/workaround (applies to all the pages you listed: Language Management, AI Management, Text Templates, Audit Logs):
- Set breadcrumbs explicitly in those pages (or in a shared base component they inherit from), using
PageLayout.BreadcrumbItems. - If you observe that breadcrumbs appear only after a refresh / navigation, then set them in
OnAfterRenderAsync(firstRender), not only inOnInitialized*. This is a known pattern in ABP Blazor to avoid the “missing title/breadcrumb until refresh” behavior.
- Set breadcrumbs explicitly in those pages (or in a shared base component they inherit from), using
Example (MudBlazor)
Add/override in the relevant
.razor.cs(or@codeblock):using MudBlazor; using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars; // if needed for PageLayout injection using Volo.Abp.AspNetCore.Components.Web.Theming.Layout; public partial class Languages // example page/component { [Inject] private PageLayout PageLayout { get; set; } = default!; protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); if (!firstRender) { return; } PageLayout.BreadcrumbItems.Clear(); PageLayout.BreadcrumbItems.Add(new BreadcrumbItem("Administration", "/administration")); PageLayout.BreadcrumbItems.Add(new BreadcrumbItem("Language Management", null, disabled: true)); PageLayout.BreadcrumbItems.Add(new BreadcrumbItem("Languages", null, disabled: true)); await InvokeAsync(StateHasChanged); } }Notes:
- Use real URLs for clickable nodes if you want (e.g., link “Language Management” to its main route if you have one).
- If you’re using
<PageHeader ... />directly in the page, you can also pass a localList<BreadcrumbItem>viaBreadcrumbItems=...instead ofPageLayout.
For “Chat Playground - WorkspaceName” and “Data Sources”
- These screens typically need dynamic breadcrumbs (workspace name, etc.). The same mechanism applies:
- Clear existing items
- Add
Administration > AI Management > Workspaces > {WorkspaceName} > Playground - For “Data Sources”, if there is no breadcrumb at all, it’s simply not being set (or it’s being set too early). Setting it in
OnAfterRenderAsync(firstRender)resolves the “not rendered” scenario.
Sources
- https://abp.io/docs/10.7/framework/ui/blazor/page-header?BlazorUI=MudBlazor
- https://abp.io/docs/10.7/framework/ui/blazor/page-layout?BlazorUI=MudBlazor
- https://abp.io/support/questions/6631
- https://abp.io/docs/10.7/framework/ui/blazor/navigation-menu
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Could a member of the support team please review this ticket?
Can we expect this to be fixed in an upcoming release, or should we try the suggested workaround in the meantime?
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
These MudBlazor pages don't set their breadcrumb items, so LeptonX only shows the page title. The Data Sources page has no page header at all, so it shows nothing. This is a bug and we will fix it in the next release. Your ticket has been refunded.
You can use this workaround for now. It replaces those pages with small subclasses that set the full menu path.
- Add two files to your
*.Blazor.Clientproject:ModuleBreadcrumbs.csbelow, andAIManagementBreadcrumbs.csin my next answer (both together are too long for one post). Change the namespace to yours. - Call
AddModuleBreadcrumbs()in both your*.Blazor.Clientmodule and your*.Blazor(server) module. The server module doesn't depend on the client module, so without the second call the pages that render on the server still show only the title. After this, the server app logs a warning that the*.Blazor.Clientassembly is not in the[DependsOn]chain. You can ignore it, these pages only use[Inject]properties.
using MyProjectName.Blazor.Client.Breadcrumbs; public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddModuleBreadcrumbs(); // ... }A few notes:
- It also covers the Organization Units page and the two text template content pages. They have the same problem.
- Chat Playground and Data Sources show
Administration > AI Management > Workspaces > WorkspaceName > Chat Playground(orData Sources). - If you go back from one workspace's page to another one through the browser history, Chat Playground and Data Sources reload once, so the page and the breadcrumb both use the workspace in the URL.
ModuleBreadcrumbs.cs:using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Localization; using MudBlazor; using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; using Volo.Abp.UI.Navigation.Localization.Resource; using Volo.Abp.AuditLogging.Blazor.MudBlazor.Pages.AuditLogging; using Volo.Abp.Identity; using Volo.Abp.Identity.Pro.Blazor.MudBlazor.Pages.Identity; using Volo.Abp.LanguageManagement.Blazor.MudBlazor.Pages.LanguageManagement; using Volo.Abp.TextTemplateManagement.Blazor.MudBlazor.Pages.TextTemplateManagement; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.Chat; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.McpServers; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.WorkspaceDataSources; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.Workspaces; namespace MyProjectName.Blazor.Client.Breadcrumbs; public static class ModuleBreadcrumbs { public static IServiceCollection AddModuleBreadcrumbs(this IServiceCollection services) { services.Replace(ServiceDescriptor.Transient<LanguageManagement, MyLanguageManagement>()); services.Replace(ServiceDescriptor.Transient<LanguageTextManagement, MyLanguageTextManagement>()); services.Replace(ServiceDescriptor.Transient<AuditLogsManagement, MyAuditLogsManagement>()); services.Replace(ServiceDescriptor.Transient<TextTemplateManagement, MyTextTemplateManagement>()); services.Replace(ServiceDescriptor.Transient<TextTemplateContentManagement, MyTextTemplateContentManagement>()); services.Replace(ServiceDescriptor.Transient<TextTemplateInlineContentManagement, MyTextTemplateInlineContentManagement>()); services.Replace(ServiceDescriptor.Transient<OrganizationUnitsManagement, MyOrganizationUnitsManagement>()); services.Replace(ServiceDescriptor.Transient<Workspaces, MyWorkspaces>()); services.Replace(ServiceDescriptor.Transient<McpServers, MyMcpServers>()); services.Replace(ServiceDescriptor.Transient<Chat, MyChat>()); services.Replace(ServiceDescriptor.Transient<WorkspaceDataSources, MyWorkspaceDataSources>()); return services; } public static BreadcrumbItem Item(string text, string? href = null) { return new BreadcrumbItem(text, href, disabled: href == null); } public static string Url(string? culture, string path) { return string.IsNullOrEmpty(culture) ? path : $"{Uri.EscapeDataString(culture)}/{path}"; } } // Remembers the items a page put into the shared PageLayout, so it can re-apply them // when another page's cleanup wiped them, and only clear them if they are still its own. public class BreadcrumbState { private BreadcrumbItem[] _items = Array.Empty<BreadcrumbItem>(); private string? _key; public void Apply(PageLayout pageLayout, params BreadcrumbItem[] items) { var key = string.Join("|", items.Select(x => x.Text + ">" + x.Href)); if (key == _key && IsOwner(pageLayout)) { return; } _key = key; _items = items; pageLayout.BreadcrumbItems.Clear(); foreach (var item in items) { pageLayout.BreadcrumbItems.Add(item); } } public void Release(PageLayout pageLayout) { if (IsOwner(pageLayout)) { pageLayout.BreadcrumbItems.Clear(); } } private bool IsOwner(PageLayout pageLayout) { return _items.Length > 0 && pageLayout.BreadcrumbItems.Count == _items.Length && pageLayout.BreadcrumbItems.Zip(_items).All(x => ReferenceEquals(x.First, x.Second)); } } // The previous page's PageHeader resets PageLayout after this page initializes during in-app // navigation, so the items are applied again after each render (a no-op when unchanged). public class MyLanguageManagement : LanguageManagement { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:Languages"]), ModuleBreadcrumbs.Item(L["Languages"])); } } public class MyLanguageTextManagement : LanguageTextManagement { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:Languages"]), ModuleBreadcrumbs.Item(L["LanguageTexts"])); } } public class MyAuditLogsManagement : AuditLogsManagement { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:AuditLogging"])); } } public class MyTextTemplateManagement : TextTemplateManagement { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:TextTemplates"])); } } public class MyTextTemplateContentManagement : TextTemplateContentManagement { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; if (TemplateDefinitionName == null) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:TextTemplates"], ModuleBreadcrumbs.Url(Culture, "text-templates")), ModuleBreadcrumbs.Item(TemplateDefinitionName)); } } public class MyTextTemplateInlineContentManagement : TextTemplateInlineContentManagement { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; if (TemplateDefinitionName == null) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:TextTemplates"], ModuleBreadcrumbs.Url(Culture, "text-templates")), ModuleBreadcrumbs.Item(TemplateDefinitionName)); } } public class MyOrganizationUnitsManagement : OrganizationUnitsManagement { // The base page already injects PageLayout / LUiNavigation as private members. [Inject] protected PageLayout BreadcrumbPageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> BreadcrumbUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); public MyOrganizationUnitsManagement(IOrganizationUnitAppService organizationUnitAppService) : base(organizationUnitAppService) { } protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(BreadcrumbPageLayout, ModuleBreadcrumbs.Item(BreadcrumbUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:IdentityManagement"]), ModuleBreadcrumbs.Item(L["OrganizationUnits"])); } }Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) - Add two files to your
-
0
Hi,
Here is the second file,
AIManagementBreadcrumbs.cs. Put it next toModuleBreadcrumbs.csin your*.Blazor.Clientproject:using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; using Volo.Abp.UI.Navigation.Localization.Resource; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.Chat; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.McpServers; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.WorkspaceDataSources; using Volo.AIManagement.Blazor.MudBlazor.Pages.AIManagement.Workspaces; namespace MyProjectName.Blazor.Client.Breadcrumbs; public class MyWorkspaces : Workspaces { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:AIManagement"]), ModuleBreadcrumbs.Item(L["Menu:Workspaces"])); } } public class MyMcpServers : McpServers { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); protected override void OnInitialized() { base.OnInitialized(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:AIManagement"]), ModuleBreadcrumbs.Item(L["Menu:McpServers"])); } } // Blazor reuses these pages when only the workspace in the URL changes (for example, picking // an older entry from the browser's back menu), but they only load the workspace once. // Reloading keeps the page and its breadcrumb on the workspace in the URL. public class MyChat : Chat { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; [Inject] protected NavigationManager ReloadNavigationManager { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); private string? _initialWorkspaceName; private bool _reloading; protected override void OnInitialized() { base.OnInitialized(); _initialWorkspaceName = WorkspaceName; ApplyBreadcrumbs(); } protected override async Task OnParametersSetAsync() { if (!_reloading && _initialWorkspaceName != null && WorkspaceName != _initialWorkspaceName) { _reloading = true; ReloadNavigationManager.NavigateTo(ReloadNavigationManager.Uri, forceLoad: true); return; } await base.OnParametersSetAsync(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed || _reloading) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:AIManagement"]), ModuleBreadcrumbs.Item(L["Menu:Workspaces"], ModuleBreadcrumbs.Url(Culture, "ai-management/workspaces")), ModuleBreadcrumbs.Item(WorkspaceName), ModuleBreadcrumbs.Item(L["ChatPlayground"])); } } public class MyWorkspaceDataSources : WorkspaceDataSources { [Inject] protected PageLayout PageLayout { get; set; } = default!; [Inject] protected IStringLocalizer<AbpUiNavigationResource> LUiNavigation { get; set; } = default!; [Inject] protected NavigationManager ReloadNavigationManager { get; set; } = default!; private readonly BreadcrumbState _breadcrumbs = new(); private Guid? _initialWorkspaceId; private bool _reloading; protected override void OnInitialized() { base.OnInitialized(); _initialWorkspaceId = WorkspaceId; } protected override async Task OnParametersSetAsync() { if (!_reloading && _initialWorkspaceId != null && WorkspaceId != _initialWorkspaceId) { _reloading = true; ReloadNavigationManager.NavigateTo(ReloadNavigationManager.Uri, forceLoad: true); return; } await base.OnParametersSetAsync(); ApplyBreadcrumbs(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); ApplyBreadcrumbs(); } private void ApplyBreadcrumbs() { if (IsDisposed || _reloading || Workspace == null) return; _breadcrumbs.Apply(PageLayout, ModuleBreadcrumbs.Item(LUiNavigation["Menu:Administration"]), ModuleBreadcrumbs.Item(L["Menu:AIManagement"]), ModuleBreadcrumbs.Item(L["Menu:Workspaces"], ModuleBreadcrumbs.Url(Culture, "ai-management/workspaces")), ModuleBreadcrumbs.Item(Workspace.Name, ModuleBreadcrumbs.Url(Culture, $"ai-management/workspaces/{Uri.EscapeDataString(Workspace.Name)}")), ModuleBreadcrumbs.Item(L["DataSources"])); } protected override void Dispose(bool disposing) { // This page has no PageHeader, so nothing else clears its items when leaving it. _breadcrumbs.Release(PageLayout); base.Dispose(disposing); } }Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)