I think the "complete" solution will also be useful for others in the future, so I made it as a community wiki. Feel free to edit as much as you want.
Thanks to the people who answered my questions here and here. I used their solutions here but with some minor adjustments.
@using System.Reflection
@using System.Text.RegularExpressions
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Notes</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick=ToggleNavMenu>
<ul class="nav flex-column">
@foreach (var name in GetRoutableComponentNamesForMenu())
{
<li class="nav-item px-3">
<NavLink class="nav-link"
href=@(name=="Index"?string.Empty:name)
Match=@(name=="Index"?NavLinkMatch.All:NavLinkMatch.Prefix)>
<span class="oi oi-home" aria-hidden="true"></span>
@Regex.Replace(name, @"(\B[A-Z])", " $1")
</NavLink>
</li>
}
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
public IEnumerable<string> GetRoutableComponentNamesForMenu()
{
var allComponents = Assembly
.GetExecutingAssembly()
.ExportedTypes
.Where(t => t.IsSubclassOf(typeof(ComponentBase)));
var routableComponents = allComponents
.Where(c => c
.GetCustomAttributes(inherit: true)
.OfType<RouteAttribute>()
.Count() > 0);
foreach (var routableComponent in routableComponents)
{
yield return routableComponent
.ToString()
// you need to adjust the following!
.Replace("Notes.Client.MenuPages.", string.Empty);
}
}
}
Note that
MenuPages
is a directory in the project that only contains routable Razor components to be displayed as menu. Error
page must not be there for sure!
The name for each components in MenuPages
are in PascalCase.
As I am new to reflection, the fully qualified name prefixes are hard coded. If you know how to make it more elegant without hard-coding, feel free to edit.