zoukankan      html  css  js  c++  java
  • 让菜单充满活力:ASP.NET根据角色动态分配菜单+权限

       这次做图书馆维护系统,首先要解决的问题就是角色权限动态分配,权限分配直接体现就是菜单的动态分配。在此和大家分享一下心得。

       大多数系统,都有多种类型的用户,不同的用户权限不同,某一个功能,A类用户是可见的,但是B类用户没有必要或者不应该看见这个功能,这就要涉及到功能的动态分配。要解决这个问题,当然要从数据下手,在学姐的指导下,有了如下的UML设计图:

     

    解释一下:

    MemberType表是用户类型表。

    SystemFunction表是系统所有功能表,记录了功能的名称和对应的页面URL,思想是一个功能即一个页面。

    Tab表是菜单表,也就是顶级菜单,SystemFunction表中的功能将被归类到这个菜单中。

    MemberFunction表是用户功能表,这个表负责连接MemberType表和Tab表,通过这个表可以得知何种用户有哪些菜单。

    TabFunction是菜单功能表,负责连接Tab表和SystemFunction表,通过这个表可以得知何种菜单有哪些功能。

       这种设计遵守了三范式设计原则,使用起来非常方便。假如我们要给某种类型的用户增加一种菜单(增加一种权限),只需要在MemberFunction表中建立一个连接即可:添加一条记录,字段值分别是该类型用户的id和对应菜单的ID。给某个菜单添加某个功能也是如此。这样一来,管理起来非常方便,只需要添加或删除MemberFunction表和TabFunction表中的记录,就可以达到灵活分配用户拥有的菜单、灵活分配菜单中的功能。

       结合ASP.NET,我们还需要把这种数据库表示转换成界面表示。在D层,必须借助于下边两个存储过程:

    GO
    /*-----------------------------用户身份(类型)对应顶级菜单表存储过程-------------------------------*/
    /*选取某种用户顶级菜单*/
    CREATE PROCEDURE proc_MemberFunction_SelectByTypeID
    	@memberTypeID bigint
    AS
    BEGIN
    	select t_MemberFunction.*,t_Tab.[name] from t_MemberFunction 
    	join t_Tab on t_Tab.id=t_MemberFunction.tabID
    	where memberTypeID=@memberTypeID
    END
    GO
    /*-----------------------------顶级菜单(选项卡)功能表存储过程-------------------------------*/
    /*选取某种顶级菜单的下属功能*/
    CREATE PROCEDURE proc_TabFunction_SelectTabFunction
    	@tabID bigint
    AS
    BEGIN
    	select t_TabFunction.*,t_SystemFunction.[name],t_SystemFunction.pageURL from t_TabFunction
    	join t_SystemFunction on t_SystemFunction.id=t_TabFunction.systemFunctionID
    	where
    	tabID=@tabID
    END

       有了这两个存储过程,就可以读出所有的菜单数据。接下来就要在界面上显示,一般情况下,界面上的菜单都是用ul和li标签,然后用javascript加以控制,在这一级菜单就可以满足我们的需求,类似下边这个结构:

    <ul>
    	<li class="menu">
    		<a href="#">个人管理</a>
    		<ul>
    			<li><a href='#'>查看信息</a></li>    
    		</ul>
    		<ul>
    			<li><a href='#'>修改密码</a></li>    
    		</ul>
    	</li>
    </ul>

       不难看出,个人管理的位置就是顶级菜单,查看信息、修改密码的位置是具体功能,很明显的一个嵌套结构(把上边的代码保存成html文件,打开看看就知道是啥样的结构了)。在界面上绑定数据,轻量级的repeater控件是非常不错的选择,具体怎么用就不赘述了。要用repeater控件显示出上边提到的结构,就必须进行repeater控件的嵌套。那么如何在ASP.NET中嵌套repeater控件呢?注:以下代码都是针对于本文的数据库,如果您想用,要改一改,起码要改改读取的字段。。。

    aspx前台文件代码:

    <ul>
                <!--读取顶级菜单-->
                <asp:Repeater ID="menuRepeater" runat="server" 
                    onitemdatabound="menuRepeater_ItemDataBound">
                    <ItemTemplate>
                        <li class="menu">
            	            <a href="#"><%# Eval("name") %></a>
                            <ul>
                                <!--读取二级菜单-->
                                <asp:Repeater ID="functionRepeater" runat="server">
                                    <ItemTemplate>
                                        <li><a href="#" onclick='javascript:changeSrc("<%# setSession(Eval("pageURL").ToString) %
    >");'><%# Eval("name") %></a></li>
                                    </ItemTemplate>
                                </asp:Repeater>  	            
                            </ul>
                        </li>
                    </ItemTemplate>
                </asp:Repeater>
    </ul>

    aspx.cs后台文件代码:

    //外层repeater数据绑定
    DataTable dt = new DataTable();
    dt = menumanager.getMemberFunction(Convert.ToInt64(Request.QueryString["memberTypeID"].ToString()));
    menuRepeater.DataSource = dt;
    menuRepeater.DataBind();
    //内层repeater数据绑定
    protected void menuRepeater_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e)
    {
        DataTable dt = new DataTable();
        Repeater functionRepeater = (Repeater)e.Item.FindControl("functionRepeater"); //找到内层的repeater控件
        DataRowView rowv = (DataRowView)e.Item.DataItem;
        dt = menumanager.getTabFunction(Convert.ToInt64(rowv["tabID"]));  //读取上一层repeater控件中保存的菜单id,并且根据该id去读取菜单下的功能
        //绑定数据
        functionRepeater.DataSource = dt; 
        functionRepeater.DataBind();
    }

       repeater嵌套就是这么简单,需要注意的是,在外层repeater上注册的是onitemdatabound事件,也就是itemtemplate模版数据绑定事件,千万不要理解成是repeater的绑定事件。然后在用onitemdatabound注册的menuRepeater_ItemDataBound事件中,去绑定内层repeater控件的数据就可以了。

       细心的读者可能会发现在aspx前台代码中调用了一个setSession函数,这个函数就是就是分配权限用的。函数内容:

    public string setSession(string pageName) 
    {
        Session["PagePermissions"] = Session["PagePermissions"].ToString() + "|" + pageName.Split(new char[1]{'.'})[0];
        return pageName;
    }
    

       这么简单的一个小函数,是如何做到分配权限的呢?地球人都知道,即使我们没有给X类型的用户显示某个功能页面,但这个页面是确确实实存在的,只是没有让X看到而已,假如X用户手动访问这个页面,如果显示出来了,不就乱了吗?通过这个函数我们可以获取所有的页面名称,把他们拼接成一个字符串,保存到session中,然后在每个页面的pageLoad事件中都检查这个session,看看这个session中有没有自己的名称,如果没有,就跳转到错误页面,如果有,就显示。这样一来,菜单分配和权限分配就一块搞定了,方便简洁!

       至此,一个ASP.NET根据角色动态分配菜单+权限的例子就讲完了,但是做完这个工程之后我发现这样还不是很好,经过仔细的分析,这样的数据库设计可以用下边这张图表示:


       可以看出,假如我们要增加一级菜单,就要额外增加两个表:一个菜单表一个连接表。这在实际应用中并不合理。用过wordpress的朋友都知道,它的菜单可以通过拖拉的方式进行排布,假如是上边这种结构,要来来回回的去删表、建表,这几乎是不敢想象的工作量。经过思考,无论是几级菜单,都放到一个表中(抽象成一个表),然后把所有的连接表也抽象成为一个表,有了如下结构:



    其中表内的结构如下(表名:字段名1,字段名2…):

    系统菜单(t_SystemMenu):id,level,menuName

    菜单衔接(t_MenuLink):id,menuID,belongToID

    数据库中读取菜单语句:

    遍历分级(确定共有多少级):

    SELECT * FROM t_SystemMenu WHERE level=@level

    选出下属菜单(选出每一级下属的菜单或功能):

    SELECT * FROM t_MenuLink WHERE belongToID=@id

       这样设计在数据库读取方面没有问题,但是界面显示就比较困难了,因为我们无法确定repeater控件的数量,有兴趣的可以google搜“动态创建repeater控件”,由于这种技术与平台有很大关系,在此我只抛砖引玉,具体的就留给读者思考了。

       PS:这种变态的设计很可能不符合数据库设计三范式,具体情况具体分析吧,有时候没必要迷信于什么范式!关系型数据库有时候还不好使呢!

  • 相关阅读:
    Reflector 插件
    Tips for ILMerge
    WaitAll for multiple handles on a STA thread is not supported 解决方案
    MSI: UAC return 0x800704C7
    SET与SETX的区别
    年在Copyright中的含义
    gacutil : 添加.NET 4.0 assembly 到GAC失败
    LicenseContext.GetSavedLicenseKey 需要 FileIOPermission
    Linq学习之linq基础知识
    SQL Server 2008如何导出带数据的脚本文件
  • 原文地址:https://www.cnblogs.com/iyangyuan/p/2801837.html
Copyright © 2011-2022 走看看