3

I want to programmatically generate a list of menus by iterating through the routable Razor component names as follows. How to implement GetAllRoutableRazorComponentNames() below? Is it possible?

@foreach (var filename in GetAllRoutableRazorComponentNames())
{
    <li class="nav-item px-3">
        <NavLink class="nav-link" href="@filename">
            <span class="oi oi-list-rich" aria-hidden="true">@filename</span>
        </NavLink>
    </li>
}
Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165
  • You'll need to use reflection to find all types with the PageAttribute – Mister Magoo Jul 01 '20 at 11:13
  • @MisterMagoo: Is there any simple way with `System.IO` classes? – Second Person Shooter Jul 01 '20 at 11:18
  • 1
    The `Router` component uses `RouteTableFactory` to do something similar. It's `internal`, but the [source](https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Components/Components/src/Routing/RouteTableFactory.cs) should be useful if you need something of your own. – Kirk Larkin Jul 01 '20 at 12:05
  • A component can have 2 or more @page attributes, and may require parameters. How will your Menu deal with that? – H H Jul 01 '20 at 13:26
  • It's just that the whole thing looks a little bit X/Y question. For instance, the standard apps have a "/error" page, you don't want that in your menu. – H H Jul 01 '20 at 16:20
  • @HenkHolterman: We need to create a special folder that is designated for menu, `MenuPages` for example. Every routable Razor page can have multiple `@page` directives so invoking them without passing parameter will invoke the default `@page "/"`. Menu just needs to invoke the default. – Second Person Shooter Jul 02 '20 at 05:10

2 Answers2

4

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.

LEJ
  • 47
  • 7
Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165
1

OK, this one returns only routable components. When I've time I'll improve on it as well. But it does what you want:

    @using System.Linq;
@using System.Reflection;


<button type="button" @onclick="GetRoutables">
    Get Route
    Url With Authorize Attribute
</button>

@code{

  public void GetRoutables()
  {
     // Get all the components whose base class is ComponentBase
    var components = Assembly.GetExecutingAssembly()
       .ExportedTypes
     .Where(t => t.IsSubclassOf(typeof(ComponentBase))).ToList();
    
    var routables = components.Where(c => 
        c.GetCustomAttributes(inherit: true).OfType<RouteAttribute> 
        ().ToArray().Count() > 0);
                   

     foreach (var routable in routables)
     {
        Console.WriteLine(routable.ToString());

     }     

    }

}

Hope this helps...

enet
  • 41,195
  • 5
  • 76
  • 113