I am trying to develop a generic functionality that can get me all the actions and all the controllers of an application. I will want to filter these controller actions by all those actions which are GET only and not POST. In other words, I want to have all the routes possible in an application. I'm doing this using reflection but haven't really been able to get through. I want to use reflection so each time any new controller and actions are added they are in this list as well.
so, why I want this is, I want to basically show these actions as a dynamic menu for a user based on their roles, claims, and/or any other user permission. This should only show them what is allowed.
Any leads in this regard will be very much appreciated.
Thank you.
[Table("Menus")]
public class Menu
{
public int ID { get; set; }
public string Name { get; set; }
public string CssClass { get; set; }
public int SortOrder { get; set; }
public MenuLevel MenuLevel { get; set; }
public List<MenuItem> MenuItems { get; set; }
}
[Table("MenuItems")]
public class MenuItem
{
public int ID { get; set; }
public string Name { get; set; }
public string CssClass { get; set; }
public int SortOrder { get; set; }
}
List<Menu> menus = new();
// loop through all potential controller types
foreach (var controllerType in Assembly.GetExecutingAssembly().GetExportedTypes().Where(t => typeof(ControllerBase).IsAssignableFrom(t)))
{
Menu menu = new()
{
Title = controllerType.Name,
MenuLevel = MenuLevel.Level1,
SortOrder = 1,
RouteAddress = @$"\{controllerType.Name}\"
};
menu.Title = menu.Title.Replace("Controller", "");
// get their all public instance methods (excluding inherited ones)
// these are the so-called action methods
foreach (var actionMethod in controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
{
// get attributes assigned with the method
var attributes = actionMethod.GetCustomAttributes(false);
// filter methods marked with [NonAction]
// filter out the ones with ref or out params too here if
// you have such things
if (!attributes.Any(i => i is NonActionAttribute))
{
// filter POSTs out as you wish
if (!attributes.Any(i => i is HttpPostAttribute))
{
// you can compose something from various info available at this point e.g.
// through 'controllerType', 'actionMethod' or other attributes if they
// exists in 'attributes' (e.g. RouteAttribute)
menu.MenuItems.Add(new MenuItem()
{
MenuID = menu.MenuID,
Title = actionMethod.Name,
RouteAddress = menu.RouteAddress $"{actionMethod.Name}"
});
}
}
}
menus.Add(menu);
}
await CheckForDatabaseExistence(menus);
return menus;
private Task<int> CheckForDatabaseExistence(List<Menu> menus)
{
var dbMenus = _context.Menus.ToList();
foreach (var menu in menus)
if (!dbMenus.Contains(menu))
{
_context.Menus.Add(menu);
return _context.SaveChangesAsync();
}
return Task.FromResult(0);
}
I have modified the code so it may help anyone in the future. Thanks to cly whose answer below helped.
CodePudding user response:
I am sure there is an other way through MVC's routing to accomplish this task as you mentioned. But you are starting your code with reflection-based way so it may be better to stay with this and not mixing approaches.
In MVC an action method could be any method of a Controller
based class, which:
- is
public
- is not
static
- has no tricky parameters, like the ones marked with
ref
andout
- not marked with
[NonAction]
attribute
The PoC code below shows you how to use these constraints to collect the data you need.
(The code was implemented as a TestMethod
because test execution gives a very handy quick micro execution environment to temporarily try out things while all things of your development domain are available. This fake "test" could be simply dropped if the thing you wanted is tried out and you move forward.)
[TestMethod]
public void MyTestMethod()
{
// loop through all potential controller types
foreach (var controllerType in Assembly
.GetExecutingAssembly()
.GetExportedTypes()
.Where(t => typeof(ControllerBase).IsAssignableFrom(t)))
{
// get their all public instance methods (excluding inherited ones)
// these are the so-called action methods
foreach(var actionMethod in controllerType.GetMethods(
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.DeclaredOnly))
{
// get attributes assigned with the method
var attributes = actionMethod.GetCustomAttributes(false);
// filter methods marked with [NonAction]
// filter out the ones with ref or out params too here if
// you have such things
if (!attributes.Any(i=>i is System.Web.Http.NonActionAttribute))
{
// filter POSTs out as you wish
if (!attributes.Any(i => i is System.Web.Http.HttpPostAttribute))
{
// you can compose something from various info available at this point e.g.
// through 'controllerType', 'actionMethod' or other attributes if they
// exists in 'attributes' (e.g. RouteAttribute)
var controllerName = controllerType.Name;
var actionName = actionMethod.Name;
}
}
}
}
}