• 如何优雅的对WinForm窗体应用程序进行权限控制


    特别复杂特别高大上的系统我还没有机会接触,就我了解的来看,普通的功能权限控制的流程都差不多只有两个过程:

    1. 获取当前用户拥有的权限
    2. 在界面上对功能入库的可用性或者可见性进行控制

    这里说一种在WinForm窗体应用开发时进行权限控制的办法,文章中主要针对上述两个过程的第二步。不过为了说清楚,我先简单说一下我的数据库功能表设计。

    数据库

    大家的权限数据库好像都差不多,我比较习惯Code First,所以就直接贴数据库对应的对象。记录下功能名称,所属模块,窗体名称,控件名称。权限表里面主要就是记录了用户(角色)ID和功能的对应了,有记录就表示该用户(角色)拥有该功能权限,没有记录就是没有权限。

    /// 
    /// 系统功能
    /// 
    [SugarTable("X_FUNCTION", "系统功能")]
    public class FUNCTION
    {
        [SugarColumn(IsPrimaryKey = true, ColumnDescription = "主键", Length = 50)]
        public string GUID { get; set; }
    
        /// 
        /// 功能名称
        /// 
        [SugarColumn(ColumnDataType = "NVARCHAR2(30)", ColumnDescription = "功能名称")]
        public string NAME
        {
            get;
            set;
        }
    
        /// 
        /// 模块ID
        /// 
        [SugarColumn(ColumnDescription = "模块ID", Length = 50)]
        public string MODULEID
        {
            get;
            set;
        }
    
        /// 
        /// 窗体名称
        /// 
        [SugarColumn(ColumnDescription = "窗体名称", Length = 50)]
        public string FORMNAME
        {
            get;
            set;
        }
    
        /// 
        /// 控件名称
        /// 
        [SugarColumn(ColumnDescription = "控件名称", Length = 50)]
        public string CONTROLNAME
        {
            get;
            set;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    如何控制

    最简单的方法,就是自己在代码里面把每个功能入口的控件都手动判断一遍,他们的name在不在有权限的功能列表的控件名称里。当然简单很有可能就代表蛋疼,有的窗口可能要控制的上百控件,得写上百if-esle,这实在是太不优雅了,如果还有多个窗口的权限需要控制,那就真是灾难了。这个时候反射就可以起到比较好的作用。我们可以创建一个继承自Form的类,在这个类里面检查权限并设置控件是否可用。我使用了DevExpress控件,所以继承自XtraForm。然后只要窗口继承CheckPowerXtraForm,就可以进行权限控制了。

    代码中有两个地方需要注意,第一是要等待创建窗口句柄再设置窗口控件的可用性。因为要等待,以及读数据库,所以不能放在主线程里面(写到这里,忽然觉得这个检查权限应该放在FormLoad事件里比较好,不用Thread.Sleep,不过要读数据库,还是不适合放在主线程)。

    /// 
    /// 检查权限
    /// 
    public class CheckPowerXtraForm : XtraForm
    {
        public CheckPowerXtraForm()
        {
            //不要再主线程运行
            Task.Run(() =>
            {
                CheckPower();
            });
        }
    
    
        /// 
        /// 检查权限
        /// 
        private async void CheckPower()
        {
            var powerDal = new PowerDAL();
            var functions = await powerDal.GetPowerFunctionsByUserAsync(Global.User.GUID);
            //获取值属于本窗体的权限控件名称
            var formControls = functions.Where(f => f.FORMNAME == this.GetType().Name).Select(f => f.CONTROLNAME).ToList();
            //等待创建窗口句柄
            while (!this.Visible)
            {
                Thread.Sleep(100);
            }
            this.BeginInvoke(new Action(()=>{
                var type = this.GetType();
                var fields = this.GetType().GetFields(BindingFlags.NonPublic|BindingFlags.Instance);
                foreach (var field in fields)
                {
                    var value = field.GetValue(this);
                    if (value is Control)
                    {
                        var control = value as Control;
                        if (!formControls.Contains(control.Name))
                        {
                            control.Enabled = false;
                        }
                    }
                    else if (value is BarItem)
                    {
                        var item = value as BarItem;
                        if (!formControls.Contains(item.Name))
                        {
                            item.Enabled = false;
                        }
                    }
                    else if (value is ToolStripMenuItem)
                    {
                        var item = value as ToolStripMenuItem;
                        if (!formControls.Contains(item.Name))
                        {
                            item.Enabled = false;
                        }
                    }
                }
            }));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    问题

    上面的做法有一个问题,窗口里面不是所有控件都需要权限控制的,有的是一些容器性的控件比如Groupbox,有的是一些是个用户就有的功能比如退出登录。按上面代码的做法,只要没有记录的,全部都会设置成不可用。所以我们需要对需要权限控制的做一个标记。有两个办法:

    1. 把所有需要控制权限的控件Enabled都事先设置成false,然后有权限的就设置成true。
    2. 给需要控制的控件设置一个特性。

    下面说说第二种做法。首先定义一个特性,里面什么都不需要写。

    /// 
    /// 检查权限的特性
    /// 
    [AttributeUsage(AttributeTargets.Field)]
    public class CheckPowerAttribute : Attribute
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后给需要控制的控件赋予这个特性。

    [CheckPowerAttribute]
    private DevExpress.XtraBars.BarButtonItem mbbi_exportexcel;
    [CheckPowerAttribute]
    private DevExpress.XtraBars.BarButtonItem mbbi_exportcad;
    [CheckPowerAttribute]
    private DevExpress.XtraBars.BarButtonItem mbbi_exportshp;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后在权限控制代码里只处理具有这个特性的控件。

    //~~~
    foreach (var field in fields)
    {
        var cattributes = field.GetCustomAttributes(typeof(CheckPowerAttribute), false);
        if (cattributes.Length != 0)
        {
            var value = field.GetValue(this);
            if (value is Control)
            {
                var control = value as Control;
                if (!formControls.Contains(control.Name))
                {
                    control.Enabled = false;
                }
            }
            else if (value is BarItem)
            {
                var item = value as BarItem;
                if (!formControls.Contains(item.Name))
                {
                    item.Enabled = false;
                }
            }
            else if (value is ToolStripMenuItem)
            {
                var item = value as ToolStripMenuItem;
                if (!formControls.Contains(item.Name))
                {
                    item.Enabled = false;
                }
            }
        }
    }
    //~~~
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    总结

    说是优雅,其实自己一边写一边都觉得有不少改进的地方,比如CheckPower()执行的位置,还有设置Enable的时候用反射设置也会简洁很多,如果有更好的改进方案欢迎评论讨论。

  • 相关阅读:
    小程序获取不到用户头像和昵称返回微信用户问题解决,即小程序授权获取用户头像规则调整的最新解决方案
    聊聊Seata分布式解决方案AT模式的实现原理
    Python使用修饰函数判断执行时间
    云原生k8s的前世今生--Docker
    JuiceFS CSI:Mount Pod 的平滑升级及其实现原理
    svn提交规范
    Hive笔记2(Hive 数据类型,DDL 数据定义,DML 数据操作)
    InnoDB和MyISAM的区别
    【数据库】Mysql中between...and引起的索引失效
    Blazor前后端框架Known功能介绍:系统安装激活及自定义
  • 原文地址:https://blog.csdn.net/zxhm001/article/details/128037605