Open fasteddys opened 2 years ago
Hey @fasteddys , Please take a look at this issue and if you had further questions, please let me know.
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 });
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.
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>
@fasteddys Thanks for sharing the code and sorry for the late reply. I think we can divide this feature into 3 sections:
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.
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
Hello @mo-esmp bump +1 😄
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?
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
_menuPartial
or _sideBar
I saw this article... public class HomeController : Controller
{
[ChildActionOnly]
public ActionResult RenderMenu()
{
return PartialView("_MenuBar");
}
}
@fasteddys I'm a bit confused. To create the menu in a dynamic way, what are the requirements?
@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.
created at compile time
from an app.config / xml file (from an exported list menu action + roles)Startup()
at runtime
by getting the list of menu (Controllers/Actions) + roles.
dynamic component
, = User defined HTML + MoRoleAllowedMenuModel & render thisUserCustomHtmlListSection(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/
Let's keep this open. It will take time but in the end, we will implement this feature.
hi bump
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.