Как найти, что Контроллеры с [Авторизовывают] атрибуты с помощью Отражения в C# (или Как создать Динамический Сайт. Главные меню)

Возможно, я должен скопировать и расширить объем прежде, чем погрузиться в вопрос о заголовке...

Я в настоящее время пишу веб-приложение в ASP.NET MVC 1.0 (хотя мне действительно устанавливали MVC 2.0 на моем ПК, таким образом, я точно не ограничиваюсь 1,0) - я запустил со стандартного проекта MVC, который имеет Ваше основное, "Добро пожаловать в ASP.NET MVC", и показывает и [Домашнюю] вкладку и вкладку [About] в верхнем правом углу. Довольно стандартный, правильно?

Я добавил 4 новых Класса контроллера, давайте назовем их "Астрономом", "Биологом", "Химиком" и "Физиком". Присоединенный к каждому новому классу контроллера [Авторизовать] атрибут.

Например, для BiologistController.cs

[Authorize(Roles = "Biologist,Admin")]
public class BiologistController : Controller
{ 
    public ActionResult Index() { return View(); }
}

Они [Авторизовывают] теги, естественно ограничивают, какой пользователь может получить доступ к различным контроллерам в зависимости от Ролей, но я хочу динамично создать Меню наверху своего веб-сайта в Сайте. Ведущее устройство Page на основе Ролей пользователь является частью. Так, например, если бы "JoeUser" был членом Ролей "Астроном" и "Физик", то в навигационном меню было бы сказано:

[Домашний] [Астроном] [Физик] [О]

И естественно, это не перечислило бы ссылки на Индексную страницу контроллера "Биолога" или "Химика".

Или если бы "JohnAdmin" был членом Роли "Администратор", то ссылки на все 4 контроллера обнаружились бы в панели навигации.

Хорошо, Вы пролил получаете идею... Теперь для реального вопроса...


Начиная с ответа от этой темы StackOverflow о здании Динамического меню в ASP.NET, я пытаюсь понять, как я полностью реализовал бы это.

Ответ предлагает Расшириться, Класс контроллера (назовите его "ExtController"), и затем имейте каждый новый WhateverController, наследовались ExtController.

Мое заключение состоит в том, что я должен был бы использовать Отражение в этом Конструкторе ExtController для определения, который имеют Классы и Методы [Разрешают] атрибуты, присоединенные к ним определять Роли. Затем с помощью Статического Словаря, сохраните Роли и Контроллеры/Методы в парах "ключ-значение".

Я воображаю это чем-то вроде этого:

public class ExtController : Controller
{
    protected static Dictionary> ControllerRolesDictionary;

    protected override void OnActionExecuted(ActionExecutedContext filterContext)   
    {   
        // build list of menu items based on user's permissions, and add it to ViewData  
        IEnumerable menu = BuildMenu();  
        ViewData["Menu"] = menu;
    }

    private IEnumerable BuildMenu()
    {
        // Code to build a menu
        SomeRoleProvider rp = new SomeRoleProvider();
        foreach (var role in rp.GetRolesForUser(HttpContext.User.Identity.Name))
        {

        }
    }

    public ExtController()
    {
        // Use this.GetType() to determine if this Controller is already in the Dictionary
        if (!ControllerRolesDictionary.ContainsKey(this.GetType()))
        {
            // If not, use Reflection to add List of Roles to Dictionary 
            // associating with Controller
        }
    }
}

Действительно ли это выполнимо? Если так, как я выполняю Отражение в конструкторе ExtController для обнаружения [Авторизовать] атрибута и связанных Ролей (если таковые имеются)

ТАКЖЕ! Не стесняйтесь выходить по этому вопросу и предлагать альтернативный способ решить этот "Динамический Сайт. Главное меню на основе Ролей" проблема. Я являюсь первым, чтобы признать, что это не может быть лучшим подходом.

9
задан Flimzy 7 April 2019 в 10:29
поделиться

2 ответа

Итак, я решил доработать свой собственный класс расширенного контроллера, как я изначально предлагал. Вот очень базовая версия. Я вижу различные способы сделать это лучше (расширить, ужесточить код и т.д.), но я решил предложить свои основные результаты, потому что я представляю, что есть много других людей, которые хотят что-то подобное, но, возможно, не хотят всех дополнительных возможностей.

public abstract class ExtController : Controller
{
    protected static Dictionary<string, List<string>> RolesControllerDictionary;
    protected override void OnActionExecuted(ActionExecutedContext filterContext)   
    {   
        // build list of menu items based on user's permissions, and add it to ViewData  
        IEnumerable<MenuItem> menu = BuildMenu();  
        ViewData["Menu"] = menu;
    }

    private IEnumerable<MenuItem> BuildMenu()
    {
        // Code to build a menu
        var dynamicMenu = new List<MenuItem>();
        SomeRoleProvider rp = new SomeRoleProvider();
        // ^^^^^INSERT DESIRED ROLE PROVIDER HERE^^^^^
        rp.Initialize("", new NameValueCollection());
        try
        {   // Get all roles for user from RoleProvider
            foreach (var role in rp.GetRolesForUser(HttpContext.User.Identity.Name))
            {   // Check if role is in dictionary
                if (RolesControllerDictionary.Keys.Contains(role))
                {   
                    var controllerList = RolesControllerDictionary[role];
                    foreach (var controller in controllerList)
                    {   // Add controller to menu only if it is not already added
                        if (dynamicMenu.Any(x => x.Text == controller))
                        { continue; }
                        else
                        { dynamicMenu.Add(new MenuItem(controller)); }
                    }
                }
            }
        }
        catch { }   // Most role providers can throw exceptions. Insert Log4NET or equiv here.   
        return dynamicMenu; 
    }

    public ExtController()
    {
        // Check if ControllerRolesDictionary is non-existant
        if (RolesControllerDictionary == null)
        {
            RolesControllerDictionary = new Dictionary<string, List<string>>();
            // If so, use Reflection to add List of all Roles associated with Controllers
            const bool allInherited = true;
            const string CONTROLLER = "Controller";
            var myAssembly = System.Reflection.Assembly.GetExecutingAssembly();

            // get List of all Controllers with [Authorize] attribute
            var controllerList = from type in myAssembly.GetTypes()
                                 where type.Name.Contains(CONTROLLER)
                                 where !type.IsAbstract
                                 let attribs = type.GetCustomAttributes(allInherited)
                                 where attribs.Any(x => x.GetType().Equals(typeof(AuthorizeAttribute)))
                                 select type;
            // Loop over all controllers
            foreach (var controller in controllerList)
            {   // Find first instance of [Authorize] attribute
                var attrib = controller.GetCustomAttributes(allInherited).First(x => x.GetType().Equals(typeof(AuthorizeAttribute))) as AuthorizeAttribute;
                foreach (var role in attrib.Roles.Split(',').AsEnumerable())
                {   // If there are Roles associated with [Authorize] iterate over them
                    if (!RolesControllerDictionary.ContainsKey(role))
                    { RolesControllerDictionary[role] = new List<string>(); }
                    // Add controller to List of controllers associated with role (removing "controller" from name)
                    RolesControllerDictionary[role].Add(controller.Name.Replace(CONTROLLER,""));
                }
            }
        }
    }
}

Чтобы использовать, просто:

  • Добавьте [Authorize(Roles="SomeRole1,SomeRole2,SomeRole3,etc."] в класс Controller
  • Замените унаследованное "Controller" на "ExtController".

Например:

[Authorize(Roles = "Biologist,Admin")]
public class BiologistController : ExtController
{
    public ActionResult Index()
    { return View(); }
}

Если вы не замените "Controller" на "ExtController", то у этого контроллера не будет динамического меню. (Это может быть полезно, в некоторых сценариях, я думаю...)

В моем Site.Master файле, я изменил "menu" секцию, чтобы она выглядела так:

<ul id="menu">              
    <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
    <%  if (ViewData.Keys.Contains("Menu"))
        {
          foreach (MenuItem menu in (IEnumerable<MenuItem>)ViewData["Menu"])
          { %>
    <li><%= Html.ActionLink(menu.Text, "Index", menu.Text)%></li>           
     <%   } 
        }   
     %>       
    <li><%= Html.ActionLink("About", "About", "Home")%></li>
</ul>

И это все! :-)

3
ответ дан 4 December 2019 в 23:38
поделиться

Я предпочитаю ставить ссылки на все в меню и создавать HtmlHelper, который проверяет, является ли ссылка доступной или нет, основываясь на атрибутах [Authorize].

3
ответ дан 4 December 2019 в 23:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: