• WPF应用中一种比较完美的权限控制设计方式


    如题近段时间 需要在wpf应用中设计一个权限控制 , 简而言之的说 你懂的 对于IT人员来说都知道的 常见的软件功能 首先要有用户 用户,然后用户属于哪个角色 ,然后各个角色都有自己的可供操作的一堆功能,当然还有其它的复杂的控制方式 我并不想弄 只搞这种比较通用的。

    首先是权限管理界面 以及数据操作 一堆功能的实现 比如 添加角色 设置权限,这个其实没啥好说的 就像你做传统的winform或者web一样 搞界面 访问数据库 做功能, 就是按部就班 。好吧 看下我用到的业务数据处理方法吧:

     1 public class UserLogic
     2 {
     3     internal UserInfo GetUserById(int id)
     4     {        }
     5 
     6     internal void AddOrSaveUser(UserInfo user)
     7     {        }
     8     internal List<UserInfo> GetDBUsers()
     9     {        }
    10 
    11     internal List<RoleInfo> GetRoles()
    12     {        }
    13 
    14     internal List<AuthorizationInfo> GetAllAuths()
    15     {        }
    16 
    17     internal RoleInfo GetRoleByID(int id)
    18     {        }
    19 
    20     internal List<AuthorizationInfo> GetAuthsByRoleID(int rid)
    21     {        }
    22 
    23     internal void SetAuthsByRoleID(int rid,List<EAuthorizationItem> auths)
    24     {        }
    25 
    26     internal int AddOrSaveRole(RoleInfo ro)
    27     {        }
    28 
    29     internal void DelRole(int id)
    30     {        }
    31 
    32     internal RoleInfo GetRoleByName(string name)
    33     {        }
    34 }

    删除角色:

     1 internal void DelRole(int id)
     2 {
     3     using (MyContext db = new MyContext())
     4     {
     5         var existR = db.roles.FirstOrDefault(r => r.ID == id);
     6         db.roles.Remove(existR);
     7 
     8         var auths = db.auths.Where(r => r.RoleID == id).ToList();
     9         db.auths.RemoveRange(auths);
    10 
    11         //已经用到了此角色 的用户 改为默认角色
    12         var users = db.users.Where(r => r.RoleID == id).ToList();
    13         var defRole = db.roles.FirstOrDefault(r => r.Name == nameof(RoleNameDefine.User));
    14         for (int i = 0; i < users.Count; i++)
    15         {
    16             users[i].RoleID = defRole.ID;
    17         }                               
    18 
    19         db.SaveChanges();
    20     }
    21 }

    设置权限:

     1 internal void SetAuthsByRoleID(int rid,List<EAuthorizationItem> auths)
     2 {
     3     using (MyContext db = new MyContext())
     4     {
     5         var existAuths= db.auths.Where(r=>r.RoleID==rid).ToList();
     6         db.auths.RemoveRange(existAuths);
     7 
     8         List<AuthorizationInfo> besave = new List<AuthorizationInfo>();
     9         for (int i = 0; i < auths.Count; i++)
    10         {
    11             besave.Add(new AuthorizationInfo()
    12             {
    13                 RoleID = rid,
    14                 AuthE = auths[i]
    15             });
    16         }
    17 
    18         db.auths.AddRange(besave);
    19         db.SaveChanges();
    20     }
    21 }

    关于最终界面的样子嘛,也没美化就这样:

    然后就是 特定操作的 权限  ,“权限” 这个东西我们以什么方式来描述  ,说白了就是 固定的字符串 比如"Add_xxInfo" "Del_xxInfo" ,再怎么我们的系统还是比较小 属于比较保守的 ,说白了就那么几个功能。不可能敞着 ,我们还是得以固定代码的方式定义这些描述  要不字符串 要不枚举。由于我自己借鉴了一种方式 可以比较方便的 完成 枚举数据 从代码 到数据库  以及界面显示 的交换。最终经过反复斟酌 我们还是选用了枚举: 

     1 public enum EAuthorizationItem
     2 {
     3     [EnumDescription("打印机或自助机信息更新")]
     4     PrinterOrTerminalUpdate,
     5     [EnumDescription("打印机或自助机删除")]
     6     PrinterOrTerminalDel,
     7     [EnumDescription("数据接口管理")]
     8     DataSourceMgt,
     9     [EnumDescription("用户信息删除")]
    10     UserDel,
    11     [EnumDescription("用户信息更新")]
    12     UserUpdate,
    13     [EnumDescription("权限管理")]
    14     RoleMgt
    15 }

    接下来的思路也是顺水推舟:
    登录的时候 就能够确定所拥有的所有权限 生成功能标识数组,在登录结果里返回到客户端 ,客户端功能界面处 传入功能标识参数 通过一个统一的入口 与登录信息里的功能标识 数组 匹配 进而确定界面此部分功能是否启用。web那一套都熟悉 我们都知道怎么做,说起来简单 其实是琢磨了好久的,这是wpf。 首先要形成统一入口,不能到处编写权限判断代码 否则就违背我们的初衷了 哪怕复制粘贴同样的也不行, 我是用的mvvm方式 的, 如果我要做的话直接在viewModel里面 编写权限判断代码 很简单 毫无难度。然后另一个 可以绑定command 他可以通过canexecute 来影响界面是否可用 ,也是不错的方式 ,但是我由于一些特殊的原因 不能使用此方式。

    说道此处最显而易见的都知道了 IsEnable=”{binding}“ ,特别说一下 通过此次的使用 让我对wpf的binding 有了一个更清晰的理解,binding 几大要素 ,source 数据源 没有指定source的时候默认以当前dataContext 一级一级的向上找 ,这也是我们使用mvvm的基本支撑。然后 还有 path ,绑定方向 和 converter不用多说了。为了这玩意儿我们也是煞费苦心。首先确定的是binding 必须要用binding ,我们要用的就是它自动化计算的功能 ,什么时候自动化计算 稍后再说。为了绑定功能标识传入参数 ,于是我们首先想到从 source入手 让其定位到一个static的东西 好处有二 ,首先static的 在一个地方统一编写就行了统一引用 维护方便不易出错,第二个有编辑提示 也就是.能.出东西来 比你硬编码字符串 不只是好点吧点。binding不都是动态值吗 我们此处却都是一个固定值 这感觉怪怪的,不要怪。我们上面说了利用他的动态计算功能 ,此处可以说明了 那就是converter ,通过熟读wpf 绑定原理过程 观察它走的路线你就会知道 最终是通过converter暴露的,对我们就在此处进行截获 。对功能标识参数与当前用户进行匹配 进而决定界面是否可用。说实话前面的你可以认为是传进来的已知参数。

    好 看看我们的绑定

    <MenuItem Header="编辑所选项" Name="me_Update" Click="me_Update_Click" IsEnabled="{Binding  UserUpdate ,Source={x:Static cd:AuthorizationItemDefine.Default},Converter={StaticResource auCOnverter}  }"></MenuItem>

    来复习下wpf的绑定原理 source是让其定位到一个静态变量 而不是当前自动分配的datacontext, 然后绑定到里面的RoleMgt属性。Source={x:Static 这个是wpf设计很nice的地方 ,我们通过一个static的静态变量 但是类是new出来的 也就是单例模式,到处绑定 。

    静态绑定定义:

     1 public class AuthorizationItemDefine : PropertyChangedBase
     2 {
     3     public static AuthorizationItemDefine Default { get { return m_Default; } }
     4     private static AuthorizationItemDefine m_Default = new AuthorizationItemDefine();
     5     AuthorizationItemDefine()
     6     {
     7     }
     8 
     9     public void RiseProperty()
    10     {
    11         OnPropertyChanged(() => PrinterOrTerminalUpdate);
    12         OnPropertyChanged(() => PrinterOrTerminalDel);
    13         OnPropertyChanged(() => DataSourceMgt);
    14         OnPropertyChanged(() => UserDel);
    15         OnPropertyChanged(() => UserUpdate);
    16         OnPropertyChanged(() => RoleMgt);
    17     }
    18     public EAuthorizationItem PrinterOrTerminalUpdate
    19     {
    20         get
    21         { return EAuthorizationItem.PrinterOrTerminalUpdate; }
    22     }
    23     public EAuthorizationItem PrinterOrTerminalDel
    24     {
    25         get
    26         { return EAuthorizationItem.PrinterOrTerminalDel; }
    27     }
    28     public EAuthorizationItem DataSourceMgt
    29     {
    30         get
    31         { return EAuthorizationItem.DataSourceMgt; }
    32     }
    33 
    34     public EAuthorizationItem UserDel
    35     {
    36         get
    37         { return EAuthorizationItem.UserDel; }
    38     }
    39     public EAuthorizationItem UserUpdate
    40     {
    41         get
    42         { return EAuthorizationItem.UserUpdate; }
    43     }
    44     public EAuthorizationItem RoleMgt
    45     {
    46         get
    47         { return EAuthorizationItem.RoleMgt; }
    48     }
    49 }

    页面需要引入,以及定义converter:

    1 xmlns:cd="clr-namespace:Common.Define;assembly=Common"
    2 xmlns:cc="clr-namespace:AutoPrintClient"
    3 <UserControl.Resources>
    4     <cc:AuthConverter x:Key="auCOnverter"/>
    5 </UserControl.Resources>

    转换器很简单,就是看登录信息里有无对应的功能标识:

     1 class AuthConverter : IValueConverter
     2 {
     3     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     4     {
     5         if (value == null)
     6             return false;
     7         if (Runtime.Default.loginInfo == null || Runtime.Default.loginInfo.Auths == null)
     8             return false;
     9         string machAu = value.ToString();
    10         if (Runtime.Default.loginInfo.Auths.Contains(machAu))
    11         {
    12             return true;
    13         }
    14         else
    15         {
    16             return false;
    17         }
    18     }
    19 
    20     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    21     {
    22         throw new NotImplementedException();
    23     }
    24 }

    我们在用户登录之处就已经把功能标识数组 附在返回的登录信息里(Auths):

     1 //用户登录
     2 var user = db.users.FirstOrDefault(r => r.LoginName == loginName && r.Password == password);
     3 if (user != null)
     4 {
     5     RoleInfo ptrU = db.roles.FirstOrDefault(r => r.ID == user.RoleID);
     6     log = new LoginInfo();
     7     log.LoginName = loginName;
     8     log.Password = password;
     9     log.LoginAt = DateTime.Now;
    10     if (ptrU != null)
    11     {
    12         log.RoleID = ptrU.ID;
    13         log.RoleName = ptrU.Name;
    14         log.RoleDescription = ptrU.Description;
    15 
    16         var auths= db.auths.Where(r => r.RoleID == log.RoleID).ToList();
    17         for (int i = 0; i < auths.Count; i++)
    18         {
    19             log.Auths.Add(auths[i].AuthE.ToString());
    20         }
    21     }
    22 }
    23 else
    24 {
    25     errorMsg = "数据库未找到对应用户记录";
    26 }

    问题又来了 ,何时进行更新

    测试发现界面一直没有更新置灰 ,最后我们跟踪发现原来是usercontrol一出现的时候 就通过converter完成了binding ,而这时候其实我们还没有登录,而converter又是一个很特殊的玩意儿。我们是无法代码手动去触发他的,通过复习binding过程 推断 还是只得从值本身出发 , 这样converter就会触发了,去更新这个"其实是一直不变"的值 是不是一种很诡异的感觉 哈哈哈哈哈哈。。通过以前的知识我们知道 onPropertyChange 会触发依赖属性更新界面 。好咧 那就是他了 我们在前面的代码里加上RiseProperty方法 在里面刷新所有属性。其实上面已经是完整形式的代码了 ,就是上面贴出来的RiseProperty()方法这里就不贴了。思路顺水推舟 我们接下来做的自然是在 登录时进行 权限刷新 各处的界面刷新,通过与上面的结合 真是神来之笔。

    登录刷新调用代码:

    1 private void Click_login(object sender, RoutedEventArgs e)
    2 {
    3     if (vm.Login(passwordBox.Password) == true)
    4     {
    5         dlg_login.Visibility = Visibility.Hidden;
    6         this.uct_Onlines.LoadFirstPage();
    7         AuthorizationItemDefine.Default.RiseProperty();
    8     }
    9 }

    最终的不同用户登录效果:

    测试发现只需再登录成功后统一刷新一下就可以了 ,各处都会 按设想的工作 ,完美。干净手段解决问题的方式 你会发现  真的 真的 真的 很爽。

     

  • 相关阅读:
    引用 Geoprocessing调用ArcToolBox工具使用总结
    MySQL权限改观何时生效
    Ubuntu下安装*.bin挨次
    创立初始的MySQL权限
    MySQL的日期和光阴范例
    MySQL存取节制, 阶段1:连接证明
    让Linux操作零碎可以Mount NTFS分区
    Citrix进级XenDesktop桌面虚拟化产物
    如何在两台MySQL数据库间完成同步
    MySQL的数字
  • 原文地址:https://www.cnblogs.com/assassinx/p/13968724.html
Copyright © 2020-2023  润新知