9

タイトルの質問に飛び込む前に、バックアップして範囲を広げる必要があるかもしれません...

現在、ASP.NET MVC 1.0でWebアプリを作成しています(ただし、PCにはMVC 2.0がインストールされているため、1.0に厳密に制限されているわけではありません)-標準のMVCプロジェクトから始めました。基本的な「ASP.NETMVCへようこそ」で、右上隅に[ホーム]タブと[バージョン情報]タブの両方が表示されます。かなり標準的ですよね?

4つの新しいコントローラークラスを追加しました。それらを「天文学者」、「生物学者」、「化学者」、「物理学者」と呼びましょう。新しい各コントローラークラスには、[Authorize]属性が付加されています。

たとえば、BioologistController.csの場合

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

これらの[承認]タグは、役割に応じて異なるコントローラーにアクセスできるユーザーを自然に制限しますが、ユーザーが属している役割に基づいて、Site.MasterページのWebサイトの上部にメニューを動的に作成したいと思います。したがって、たとえば、「JoeUser」がロール「Astronomer」および「Physicist」のメンバーである場合、ナビゲーションメニューには次のように表示されます。

[ホーム][天文学者][物理学者][概要]

そして当然、「生物学者」または「化学者」コントローラーのインデックスページへのリンクはリストされません。

または、「JohnAdmin」がロール「Admin」のメンバーである場合、4つのコントローラーすべてへのリンクがナビゲーションバーに表示されます。

わかりました、あなたはその考えを大いに理解します...今本当の質問のために...


ASP.NETでの動的メニューの構築に関するこのStackOverflowトピックからの回答から始めて、これを完全に実装する方法を理解しようとしています。(私は初心者で、もう少しガイダンスが必要なので、私と一緒に裸にしてください。)

答えは、Controllerクラス(「ExtController」と呼びます)を拡張してから、新しいWhateverControllerをExtControllerから継承することを提案しています。

私の結論は、このExtControllerコンストラクターでReflectionを使用して、ロールを決定するために[Authorize]属性が付加されているクラスとメソッドを決定する必要があるということです。次に、静的ディクショナリを使用して、ロールとコントローラ/メソッドをキーと値のペアで保存します。

私はそれを次のように想像します:

public class ExtController : Controller
{
    protected static Dictionary<Type,List<string>> ControllerRolesDictionary;

    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
        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コンストラクターでReflectionを実行して、[Authorize]属性と関連するロール(存在する場合)を検出するにはどうすればよいですか。

また!この質問の範囲を超えて、この「役割に基づく動的Site.Masterメニュー」の問題を解決する別の方法を提案してください。私はこれが最善のアプローチではないかもしれないことを認める最初の人です。

編集

たくさん読んで実験した後、私は自分自身の解決策を思いつきました。私の答えは以下を参照してください。建設的なフィードバック/批評は大歓迎です!

4

3 に答える 3

3

メニュー内のすべてにリンクし、[Authorize]属性に基づいてリンクにアクセスできるかどうかを確認するHtmlHelperを作成することを好みます。

于 2010-06-08T18:05:24.647 に答える
3

さて、私は最初に提案したように、自分の拡張コントローラークラスを具体化することにしました。これは非常に基本的なバージョンです。これを改善するさまざまな方法(さらに拡張する、コードを強化するなど)を見ることができますが、似たようなものを望んでいるが、すべてを望んでいない可能性がある他の人々がたくさんいると思うので、基本的な結果を提供すると思いましたエキストラ。

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」を「ExtController」に置き換えます。

例えば:

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

「Controller」を「ExtController」に置き換えないと、そのコントローラーには動的メニューがありません。(これは、いくつかのシナリオでは役立つと思います...)

Site.Masterファイルで、「メニュー」セクションを次のように変更しました。

<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>

以上です!:-)

于 2010-06-11T13:56:30.390 に答える
0

ロジックをコントローラー側にとどまらせる必要がある同じ問題に遭遇しました。しかし、アクションが許可されているかどうかを判断するためにシステムフィルターを使用するため、ジョンのアプローチは気に入っています。それが誰かを助ける場合に備えて、次のコードHtmlHelperはジョンのアプローチからを削除しました:

    protected bool HasActionPermission(string actionName, string controllerName)
    {
        if (string.IsNullOrWhiteSpace(controllerName))
            return false;

        var controller = GetControllerByName(ControllerContext.RequestContext, controllerName);
        var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
        var actionDescriptor = controllerDescriptor.FindAction(ControllerContext, actionName);
        return ActionIsAuthorized(ControllerContext, actionDescriptor);
    }

    private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        if (actionDescriptor == null)
            return false; // action does not exist so say yes - should we authorise this?!

        AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);

        // run each auth filter until on fails
        // performance could be improved by some caching
        foreach (var filter in FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor))
        {
            var authFilter = filter.Instance as IAuthorizationFilter;

            if (authFilter == null)
                continue;

            authFilter.OnAuthorization(authContext);

            if (authContext.Result != null)
                return false;
        }

        return true;
    }

    private static ControllerBase GetControllerByName(RequestContext context, string controllerName)
    {
        IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();

        IController controller = factory.CreateController(context, controllerName);

        if (controller == null)
        {
            throw new InvalidOperationException(

                String.Format(
                    CultureInfo.CurrentUICulture,
                    "Controller factory {0} controller {1} returned null",
                    factory.GetType(),
                    controllerName));
        }
        return (ControllerBase)controller;
    }
于 2019-05-29T18:40:00.540 に答える