http://blog.csdn.net/CodingMouse/archive/2008/12/14/3515969.aspx
浅谈C# WinForm中实现基于角色的权限菜单
作者:CodingMouse 日期:2008年12月14日
转载请注明文章来源:http://blog.csdn.net/CodingMouse/archive/2008/12/14/3515969.aspx
本博文完整源码已经上传,欢迎下载讨论:http://download.csdn.net/source/913308
基于角色的权限菜单功能的实现在有开发经验的程序员看来仅仅是小菜一碟,然而却让许多初学者苦不堪言。为此,我将我近期花了几天时间写的权 限菜单写成文字贴上博客给初学者参考。由于自己也是一个正在努力学习的菜鸟,对问题的分析和见解必然不够透彻,还望过路的老师们多多批评为谢!
一、建立 SQL Server 数据库模型
1、原始方案
一共设立了五个实体模型:
A、操作员(Operator):存储系统登录用户的名称、 密码、启用状态等信息。
B、权限组(RightsGroup):存储系统权限分组(即:权限角色)的名称等信息。
C、权限关系 (RightsRelation):用于将A项和B项之间的多对多关系拆解成两个一对多关系。
D、权限列表(RightsList):存储系 统菜单项(即:权限项)的标题、内部名称、权限状态等信息。
E、权限组关系(RightGroupRelation):用于将B项和D项之间 的多对多关系拆解成两个一对多关系。
通过上面的描述可以清楚看到,C项和E项仅仅是为了拆解多对多关系而设立,实体关系变得相对 复杂了点。稍作考虑便知,既然是使用 C# WinForm + SQL Server 来完成这一功能,则可以考虑使用实体类来模拟数据库模型,并将实体类打包成泛型集合后存储到 SQL Server 数据库的 varBinary(max) 字段。这样便可以将原始方案的数据库模型缩减成三个实体模型,降低了关系的复杂程度。将原始方案稍作修改后即可得到如下改进方案。
2、 改进方案
如上所述,改进后的方案仅包含如下三个实体模型:
A、操作员(Operator):存储系统登 录用户的名称、密码、启用状态、权限集合等信息。
B、权限组(RightsGroup):存储系统权限分组(即:权限角色)的名称、权限集合 等信息。
C、权限关系(RightsRelation):用于将A项和B项之间的多对多关系拆解成两个一对多关系。
很 容易看出,仅将原始方案的 E 项更改为 A项和 B 项的字段,即可将实体关系复杂度降低 40%。现在我们来看看改进方案的 SQL Server 数据库实现脚本代码:
- -- 判断是否存在 操作员信息表 (Operator),如果存在,则删除表 Operator
- if exists(Select * From SysObjects Where Name = 'Operator' )
- Drop Table [Operator]
- Go
- -- 创建 操作员信息表 (Operator)
- Create Table [Operator]
- (
- -- 主键列,自动增 长 标识种子为 1
- [Id] int identity( 1 , 1 ) Constraint [PK_OperatorId] Primary Key,
- -- 操作员姓名
- [OperatorName] nVarChar(50 ) Constraint [UQ_OperatorName] Unique(OperatorName) Not Null,
- -- 密码
- [Password] nVarChar(50 ) Constraint [CK_Password] Check(len([Password])>= 6 ) Not Null,
- -- 操作员权限列表
- [RightsList] varBinary(max) Null,
- -- 用户当前状态
- [State] bit Constraint [DF_State] Default('true' ) Constraint [CK_State] Check([State] in ( 'true' , 'false' )) Not Null,
- )
- Go
- -- 判断是否存在 权限组信息表 (RightsGroup),如果存在,则删除表 RightsGroup
- if exists(Select * From SysObjects Where Name = 'RightsGroup' )
- Drop Table [RightsGroup]
- Go
- -- 创建 权限组信息表 (RightsGroup)
- Create Table [RightsGroup]
- (
- -- 主键列,自动增 长 标识种子为 1
- [Id] int Identity( 1 , 1 ) Constraint [PK_RightsGroupId] Primary Key,
- -- 权限组名称
- [GroupName] nVarChar(50 ) Constraint[UQ_GroupName] Unique (GroupName) Not Null,
- -- 组权限列表
- [GroupRightsList] varBinary(max) Null,
- )
- Go
- -- 判断是否存在权限关系表 (RightsRelation),如果存在,则删除表 RightsRelation
- if exists(Select * From SysObjects Where Name = 'RightsRelation' )
- drop table [RightsRelation]
- Go
- -- 创建 权限关系表(RightsRelation)
- Create Table [RightsRelation]
- (
- -- 主键列,自动增长 标 识种子为 1
- [Id] int Identity( 1 , 1 ) Constraint [PK_RightsRelationId] Primary Key,
- -- 操作员 Id
- [OperatorId] int Constraint [FK_OperatorId] Foreign Key References Operator([Id]) Not Null,
- -- 权限组 Id
- [RightsGroupId] int Constraint [FK_RightsGroupId] Foreign Key References RightsGroup([Id]) Not Null
- )
- Go
二、建立实体类
建立了 SQL Server 数据库模型之后,我们开始建立实体类来作为权限管理的数据传输载体。
1、实体模型基类 (ModelBase)
三、具体代码实现
采用多层开发模式有助于降低耦合度,便于程序维护。所以,我们的本文的具体代码实现也使用了多层开发模式。限于篇幅,只列举出具体的代码实现类源 码。同时,也是由于本文并不是讲解多层开发的文章,所以对于完成本文的主题功能所涉及的简单工厂、抽象工厂、接口定义、数据库访问等类的源码就不再一一罗 列。
(一)数据访问层
1、操作员数据访问操作类(OperatorService)
四、文章结语
以上内容并未提供完整的源代码演示,仅仅提供一种解决此类问题的思路和方法。
(一)技术难点
1、将集合 对象存储到数据库中(先使用序列化将集合对象流化,再将其转换为 Byte[] 数组);
2、使用递归法解析菜单对象及应用权限菜单项的权限设 置(注意 MenuStrip 对象的第一层级的子项集合为 Items ,其余均为 DropDownItems ,所以在递归解析或应用设置时只能从第二层级的子项开始进行递归调用);
3、BindingSource 界面数据绑定对象 与 DataGridView 数据视图控件的配合使用(注意在绑定 Dictionary<Key, T> 泛型集合内容时,应直接将 BindingSource 的 DataSource 属性设置为该集合的 .Values 才能绑定到实际的内容);
4、TreeView 树形视图控件的使用(注意在增删树节点时应适当使用 BeginUpdate() 和 EndUpdate() 方法来防止闪屏问题);
5、 隶属于多个权限组的操作员权限应为其所从属的多个权限组权限的并集(不算难,遍历一下集合即可)。
(二)扩展思考
此外,我所阐述的这种解决方案还可以再扩展一些人性化的功能。譬如本文没提及的一些人性化功能:
1、 让管理员用户完全自定义各个操作员的权限菜单标题(我已在自己的项目扩展此功能);
2、让权限菜单项在禁用时同时将该项的 Enabled 属性设置为 False ,以防止被隐藏的菜单项被快捷键呼出(太简单了,读者自己在上面的代码中添加少量代码即可实现)。
3、让管理员自由选 择使用基于用户或基于角色的权限管理方案(由于在操作员和权限组两个实体模型中都各自存储了自身的权限集合,所以,在这种相对松散的权限关系下,很容易实 现此功能,此功能我也已在自己的项目中实现)。
附上我的截图:
完整源码下载讨论:http://download.csdn.net/source/913308