Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get List<Menu> based on user role or claim #30

Open
fasteddys opened this issue Jun 11, 2022 · 13 comments
Open

Get List<Menu> based on user role or claim #30

fasteddys opened this issue Jun 11, 2022 · 13 comments

Comments

@fasteddys
Copy link

Hello, thanks for this, its very creative approach and unique, is it possible to build a menu hierarchy based on the users roles/claims at startup/cached, could you please show us so I can tie the Mo-esmpMenuHelper into a _SideBarLayout

I can help with adding the BS 5 styles with a PR if you want.

@mo-esmp
Copy link
Owner

mo-esmp commented Jun 12, 2022

Hey @fasteddys ,
Please take a look at this issue and if you had further questions, please let me know.

@fasteddys
Copy link
Author

Thanks for that @mo-esmp this is a great lib, very smart and intelligent.

Since a user can have more than one roles, I was hoping for something just a little bit different, in the previous approach the entire menu is exposed even to unauthorized users, I understand they will be blocked when they access.

GetControllersActionsByRole()
GetControllersActionsByRoleFromCache()

Is there a way to get just the list/map collection of allowed by that role

Previously I used to do this... which is not really helpful, because I need get all users roles and then find the controller/actions.

var users  = UserManager.Users.Where(x=>x.Roles.Any(y=>y.RoleId==role.Id))
                    .Select(x => new {UserId = x.Id,FullName = x.FullName })
                    // how to get the controllers
                    .Select(z => new {controllerId= x.Id });

@mo-esmp
Copy link
Owner

mo-esmp commented Jun 15, 2022

You mean with secure-content tag helper still menu will be exposed to all users even unauthorized ones?
I didn't get what you mean exactly, you're mentioning this library or your implementation. This library saves the controllers and actions for each role in RoleAccess table and then checks the requested controller/action with RoleAccess tables.

@fasteddys
Copy link
Author

fasteddys commented Jul 7, 2022

Hello @mo-esmp, please correct me if I am wrong.

Adding secure-content I think your approach is static where I have to create HTML first.
I wanted to generate the menus dynamically, since they could be role based etc. I was thinking by adding this GetControllersActionsByRole() ,
GetControllersActionsByRoleFromCache() and maybe breadcrumbs from below lib would be nice too.

Feel free to checkout Breadcrumbs https://github.com/zHaytam/SmartBreadcrumbs


Here is a some sample stuff I was trying to use, but the ASP Core MVC views does not have any good options.

Let me also share with you that your lib. is so flexible, I can manage parts of view, for e.g. inside a dashboard I can manage widget access as well!! 👍 very cool

[Generator]
public class MenuPagesGenerator : ISourceGenerator
{
    private const string RouteAttributeName = "Microsoft.AspNetCore.Components.RouteAttribute";
    private const string DescriptionAttributeName = "System.ComponentModel.Description";
    
    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
        try
        {
            var menuComponents = GetMenuComponents(context.Compilation);

            var pageDetailsSource = SourceText.From(Templates.MenuPages(menuComponents), Encoding.UTF8);
            context.AddSource("PageDetails", pageDetailsSource);
            context.AddSource("PageDetail", SourceText.From(Templates.PageDetail(), Encoding.UTF8));
        }
        catch (Exception)
        {
            Debugger.Launch();
        }
    }

    private static ImmutableArray<RouteableComponent> GetMenuComponents(Compilation compilation)
    {
        // Get all classes
        IEnumerable<SyntaxNode> allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
        IEnumerable<ClassDeclarationSyntax> allClasses = allNodes
            .Where(d => d.IsKind(SyntaxKind.ClassDeclaration))
            .OfType<ClassDeclarationSyntax>();
        
        return allClasses
            .Select(component => TryGetMenuComponent(compilation, component))
            .Where(page => page is not null)
            .Cast<RouteableComponent>()// stops the nullable lies
            .ToImmutableArray();
    }
    
    private static RouteableComponent? TryGetMenuComponent(Compilation compilation, ClassDeclarationSyntax component)
    {
        var attributes = component.AttributeLists
            .SelectMany(x => x.Attributes)
            .Where(attr => 
                attr.Name.ToString() == RouteAttributeName
                || attr.Name.ToString() == DescriptionAttributeName)
            .ToList();

        var routeAttribute = attributes.FirstOrDefault(attr => attr.Name.ToString() == RouteAttributeName);
        var descriptionAttribute = attributes.FirstOrDefault(attr => attr.Name.ToString() == DescriptionAttributeName);

        if (routeAttribute is null || descriptionAttribute is null)
        {
            return null;
        }
            
        if (
            routeAttribute.ArgumentList?.Arguments.Count != 1 ||
            descriptionAttribute.ArgumentList?.Arguments.Count != 1)
        {
            // no route path or description value
            return null;
        }
            
        var semanticModel = compilation.GetSemanticModel(component.SyntaxTree);

        var routeArg = routeAttribute.ArgumentList.Arguments[0];
        var routeExpr = routeArg.Expression;
        var routeTemplate = semanticModel.GetConstantValue(routeExpr).ToString();
        
        var descriptionArg = descriptionAttribute.ArgumentList.Arguments[0];
        var descriptionExpr = descriptionArg.Expression;
        var title = semanticModel.GetConstantValue(descriptionExpr).ToString();

        return new RouteableComponent(routeTemplate, title);
    }
}

https://andrewlock.net/using-source-generators-to-generate-a-nav-component-in-a-blazor-app/


Currently.. similarl concept.. but I want to make this fully dynami, resuable so others can also use in the lib

<li class="dropdown">
    <a asp-controller="Drink" // get controller names
       asp-action="Index"
       class="dropdown-toggle" data-toggle="dropdown">Drinks<b class="caret"></b></a>
    <ul class="dropdown-menu">
        @foreach (var category in Model)
        {
            <li>
                <a asp-controller="Drink" asp-action="List"
                   asp-route-category="@category.CategoryName">@category.CategoryName</a>
            </li>
        }
        <li class="divider"></li>
        <li>
            <a asp-controller="Drink" asp-action="List" asp-route-category="">View all drinks</a>
        </li>
    </ul>
</li>

@mo-esmp
Copy link
Owner

mo-esmp commented Jul 9, 2022

@fasteddys Thanks for sharing the code and sorry for the late reply. I think we can divide this feature into 3 sections:

  1. A service to return a model that contains a list controller, action and name to generate links
  2. A tag helper or generator to render the menu (have no idea how the HTML part should look like? does it fit all?)
  3. Adding cash to improve performance

I can help with the implementation of 1 and 3 :)
It would be great if create a sample project and assemble all codes in a project to see how they're working.

@fasteddys
Copy link
Author

thanks @mo-esmp sure, I can test it, happy to see you open to new ideas and pull our code snippets into one consolidate idea!! 🤗 it

@fasteddys
Copy link
Author

Hello @mo-esmp bump +1 😄

@mo-esmp
Copy link
Owner

mo-esmp commented Aug 24, 2022

Hey @fasteddys,

I guess I can start implementation this weekend but before that do think IMvcControllerDiscovery and MvcControllerInfo can help in case of having list of controllers and actions to generate links?

@fasteddys
Copy link
Author

Sure, I trust your design approach, looks interesting. For our users, lets do that & allow our developers who add our ms-es nuget package to easily add the menu with role/claims functionality

  1. Register in the program/configure section
  2. And generate a simple _menuPartial or _sideBar I saw this article...

public class HomeController : Controller
{
    [ChildActionOnly]
    public ActionResult RenderMenu()
    {
        return PartialView("_MenuBar");
    }
}

@mo-esmp
Copy link
Owner

mo-esmp commented Aug 29, 2022

@fasteddys I'm a bit confused. To create the menu in a dynamic way, what are the requirements?

  • For now, there is a method to get the list of controllers and actions for generating the menu, what else is required except caching?
  • The main question is how will the user change or inject HTML for creating the menu?

@fasteddys
Copy link
Author

fasteddys commented Sep 2, 2022

@mo-esmp not the expert here, but lets just start with something straight forward _PartialMenu or menu ViewComponent that's perhaps a MenuRolesHashMap. Let others chime in with their ideas as it gets some use.

  1. Either a Menu created at compile time from an app.config / xml file (from an exported list menu action + roles)
  2. or created using the Startup() at runtime by getting the list of menu (Controllers/Actions) + roles.

To weave HTML, here's a couple of options either the template engine or pull it via a dynamic component, = User defined HTML + MoRoleAllowedMenuModel & render this

  1. templating engines like https://github.com/sebastienros/fluid
  2. https://colorlib.com/wp/top-templating-engines-for-javascript/
  3. Or we could, let the user create a UserCustomHtmlListSection(and pass in your ** MoMenuModelForCUrrentRole), which we can pull as a Render in our _layout/Master
 var htmlWriter = new HtmlTextWriter(stringWriter);
        base.Render(htmlWriter);

Breadcrumbs Sample -
https://github.com/zHaytam/SmartBreadcrumbs
https://blog.zhaytam.com/2018/06/24/asp-net-core-using-smartbreadcrumbs/
https://dotnetthoughts.net/implementing-breadcrumbs-in-aspnetcore/

@mo-esmp mo-esmp reopened this Sep 8, 2022
@mo-esmp
Copy link
Owner

mo-esmp commented Sep 8, 2022

Let's keep this open. It will take time but in the end, we will implement this feature.

@fasteddys
Copy link
Author

hi bump

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants