这是松结对编程的第20篇(专栏目录)。

本文探讨Link访问权限的最佳实现方法,力求外观干净且封装良好。

这些代码将位于L型代码结构(参见松结对编程系列中的定义)的下层,调用者无需理解其原理。

顺便说一下,我们做的是管理信息系统,和互联网社区软件的一个区别是很多链接都是需要特定权限才能访问的,有些权限也不是非常直观能猜到应该具备何种条件才能访问,另外一些权限还会经常改动。因此一个容易使用、容易维护、不容易出错的权限机制尤为重要。

无权访问时该显示什么

在说实现方法之前,先说说如果链接访问条件不满足,应该显示什么。

实践发现显示文字不好,因为文字(这里应该是一段解释为何不能访问的文字)肯定比链接长,显示空间不好。

什么都不显示如何?也不太好,用户可能会误以为没有这样一个功能,或链接不在这个页面上而去其他页面寻找。

最后现在是显示一个灰色的链接,悬停时解释需要什么条件才能访问这个链接(这样用户如果想操作它但却没有权限,就会知道该怎么办)。

显示方法

方法1:散装代码

一般而言,如果要限制一个Link的访问权限,都是这样的:

if (condiction)
{@link
}
else
{@text //灰色代码,或干脆什么都不显示。
}

这样的最大坏处是,如果一个页面上有很多链接(比如导航页面),那么遍地都是if-else,眼花缭乱。

而且一旦权限修改了,就要到处修改所有可能引用过的地方。

方法2:封装Link

下面是我们原来封装的Link,里边有两个参数:
displayAsLink,即何种条件下应该显示为Link,否则将显示为灰色文字。比如product.IsProductManager()是问当前用户(括号内无值时自动采用当前用户)是否是产品经理。是,才显示链接。
grayTextTitle,显示为灰色文字时,以悬停文字解释为何不能访问。
注意还有一个displayAsBoardTextOnPage:this(this是当前Page)是问当前Page是否就是链接所在地,如果是就显示为黑体文字而不在显示连接了。
        @MFCUI.ImageLink("只读树", "/ProductManagement/StoryTrees/IndexTree?" + Request.QueryString,displayAsBoldTextOnPage: this, title: "只读故事树,速度较快")@MFCUI.ImageLink("操作树","/ProductManagement/StoryTrees/OperateTree?" + Request.QueryString,displayAsBoldTextOnPage: this, title: "可拖拽和执行所有操作,速度较慢",displayAsLink: product.IsProductManager(),grayTextTitle: "需要是此产品的产品经理才能操作。")@MFCUI.ImageLink("详情树","/ProductManagement/StoryTrees/DetailsTree?" + Request.QueryString,displayAsBoldTextOnPage: this, title: "适合快速查看所有故事的详情")@MFCUI.ImageLink("编辑树","/ProductManagement/StoryTrees/EditTree?" + Request.QueryString,displayAsBoldTextOnPage: this, title: "适合连续编辑多个故事的数据",displayAsLink: product.IsProductManager(),grayTextTitle: "需要是此产品的产品经理才能操作。")

这个方法解决了前面提到的if-else满天飞的问题,但是要解决缺陷更改造成的代码改动还不行。
此外,一些解释性的语言如“需要时此产品的产品经理才能操作”也存在多处文字的统一问题;甚至即使在同一地方,如果displayAsLink修改了条件,而grayTextTitle没有相应修改,会造成用户的错误理解。

方法3:封装模型

其实上面四句话中,能访问或不能访问,都是关于product的写操作权限的,那么如果用product自己来判断,那么代码就会集中在product内部,由专业维护此类的人员来确定权限和解释。
这个是现在为止最佳的选择。
当前View调用处的代码如下:
        @product.IndexTreeLink(this)   @product.OperateTreeLink(this)   @product.DetailsTreeLink(this)   @product.EditTreeLink(this)

Product类中代码如下:

    public partial class Product : Item{public MvcHtmlString IndexTreeLink(WebViewPage page){var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;return MFCUI.ImageLink("只读故事树","/ProductManagement/StoryTrees/IndexTree?" + queryString,displayAsBoldTextOnPage: page, title: "只读故事树,速度较快");}public MvcHtmlString OperateTreeLink(WebViewPage page){var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;return MFCUI.ImageLink("操作故事树","/ProductManagement/StoryTrees/OperateTree?" + queryString,displayAsBoldTextOnPage: page, title: "可拖拽和执行所有操作,速度较慢",displayAsLink: IsProductManager(),grayTextTitle: "需要是此产品的产品经理才能操作。");}public MvcHtmlString DetailsTreeLink(WebViewPage page){var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;return MFCUI.ImageLink("详情故事树","/ProductManagement/StoryTrees/DetailsTree?" + queryString,displayAsBoldTextOnPage: page, title: "适合快速查看所有故事的详情");}public MvcHtmlString EditTreeLink(WebViewPage page){var queryString = page.Request.QueryString.ToString().Contains("rootID") ? page.Request.QueryString.ToString() : "rootID=" + ID;return MFCUI.ImageLink("编辑故事树","/ProductManagement/StoryTrees/EditTree?" + queryString,displayAsBoldTextOnPage: page, title: "适合连续编辑多个故事的数据",displayAsLink: IsProductManager(),grayTextTitle: "需要是此产品的产品经理才能操作。");}}

刚才查找替换了一下,每个链接都出现过6次。通过改写后,原来有很多可能导致不一致的参数的调用都变成一个只传输(this)的参数了,未来维护会简单地多。

下面是一个具体的实现效果:

方法4:重写MVC的Authorize属性

方法3虽然很好,但是只是隐藏了链接而已,真正要访问,手工输入链接仍然可以。
asp.net 为我们封装了一个Authorize的属性,可以这样来简单阻止非法访问(第一行代码):
        [Authorize(Roles = "ProductManager")]public ActionResult OperateTree(int rootID, string whats, string whattypes){...var root = _repository.ReadItemAt(rootID);var product = root is Product ? root as Product : root.YoungestAncesstor<Product>();if (!product.IsProductManager())return Content("只有此产品的产品经理才可以操作。");return OperateTreeView(...);}

可惜有这么几个限制:

1. 只提供Users(常量的用户名,基本没什么用)和Roles(上例中的)两种。
比如上例中“此产品的产品经理”,就只能编码实现。
2. (似乎)没有地方查看某个Action具体访问权限是什么
也就是说,不能在别处生成指向此Action的链接时,自动根据所限定的Users或Roles来自动决定显示为链接或文字。
重写Authorize可以根据自己的条件来限制访问,唯一的问题是“自己的条件”如果太多,那么会很复杂。
还好,到现在为止火星人中就用了两种限制:
1. 某人是某个角色。
比如SiteUsersController只有Admin才可以访问,这个是站点的用户管理功能。
2. 某人是某物的某个角色。
现在火星人中,“某物”包括产品(的产品经理)、团队(的项目经理、项目助理经理即项目经理不在时应急用的代理人、项目组员)、用户故事(的负责人、当前负责人、创建者)、缺陷(的创建人、当前负责人)……,未来还有部门(的经理、部门助理经理、部门人员)……这些。
这些虽然听起来很多,但是还好之前为了存储问题,产品、团队、用户故事、缺陷……这些都是从基类UDCable(User Defined Column-able,“可被用户自定义字段的”)派生的,而刚才说的一大堆角色,都是一个个UDCType(User Defined Column Type,“用户自定义字段类型”),这样其实所有刚才说的判断,都是一种,就是问某人的Id是否等于某个UDCable的某个UDCType字段数值。
现在还没时间写这个Authroize(主要是不会写,呵呵),估计写好后使用方法如下:
        [Authorize(Roles = "ProductManager", UDCRoles="rootID, ProductManager")]public ActionResult EditTree(int rootID, string whats, string whattypes){...return OperateTreeView(...);}

UDCRoles="rootID, ProductManager"是说,用url的rootID数值来找UDCable,然后判断其"ProductManager"是否等于当前用户。

用这个属性后Action中可以减少3行(一共才5行,所以3行很多了),而整个代码中有很多这样的三行代码,估计现在有30处左右,都很容易写错造成漏洞。

剩下一个问题,@MFCUI.ImageLink怎么知道这些Action的访问权限呢?

现在的想法是在属性代码中用"Area/Controller/Action"作为Key,权限设置(就是“rootID, ProductManager”)作为值做一个静态缓存,ImageLink会根据自己传入的Url解析出“Area/Controller/Action”并去查找缓存的值,如果找到就根据权限进行判断是否显示为链接)。这样未来只要在Action前面写好属性,所有指向它的链接都会自动判断。

因为之前已经有很多可用的代码了(比如解析A/C/T的代码),所以估计两者加起来大约有10~15行代码就能实现。

估计这些代码两个月后才会排到足够优先级,写好了我共享一下。

总结

所有代码结构中的第一块积木是最难的,如果不是我们原来有一些复用了,上面这个访问权限问题解决起来可能需要上百行代码,很容易将就一下就硬编码过去了。

如果我们当年所有View里边的链接都是用<a></a>硬编码的,或用MVC中自带的Hmtl.Link()写的,那么我们也没有勇气和动力来用“这么复杂”的方式来解决这个访问权限问题了。但若干时间后,一旦访问权限变化了,肯定会因为各地的硬编码而出现无数问题,那时候就真的乱了。

UDCable和ImageLink这些都是接近一年半年前产生的,那时候完全没想到会与现在要做的权限控制相关。只能说,如果做对了事情,回报是迟早的。

所以应该随时随地把可复用的东西总结起来,这样反而不觉得累,才能不断在原来的基础上前进。

L型代码结构案例:Link访问权限(上)相关推荐

  1. 敏捷开发松结对编程系列:L型代码结构案例StatusFiltersDropdownList(下)

    这是松结对编程的第23篇(专栏目录). 接上文,45分钟后-- 新的筛选效果 现在需要在下拉框上加上两排新的筛选项(更早和更晚): 师傅本人可以在45分钟完成(实测),但如果直接交给徒弟维护(或师傅离 ...

  2. 敏捷开发松结对编程系列:L型代码结构案例StatusFiltersDropdownList(上)

    这是松结对编程的第21篇(专栏目录). 本文以一个完整案例展示代码分层及在松结对编程组中师傅与徒弟的分工. 松结对编程(以及结对编程.代码审查--等)是最末级的管理实践,其实施过程必须与工程实践相结合 ...

  3. 敏捷开发“松结对编程”系列之十一:L型代码结构(团队篇之一)

    本文是"松结对编程"系列的第十一篇.(松结对编程栏目目录) 上一篇中提到的技术方法都不太难,但问题是为什么很多团队做不到呢?问题在于: 高手可以每次都写出可复用的代码,从而大大地降 ...

  4. 敏捷开发“松结对编程”系列之十五:L型代码结构(编程篇之一)

    本文是"松结对编程"系列的第十五篇.(松结对编程栏目目录) 之前的L型代码结构的前三篇提到过,L型代码结构的微观计划和估算过程会与一般的编程方法不同,今天正好要编写一些新代码,边写 ...

  5. 敏捷开发“松结对编程”系列之十二:L型代码结构(质量篇之一)

    本文是"松结对编程"系列的第十二篇.(松结对编程栏目目录) 有没有一种管理方法,无需额外的测试活动,就能大幅度提高产品质量?L型代码结构就是其中一种候选方案. 缺陷的来源 要减少缺 ...

  6. 《Linux高性能服务器编程》学习总结(四)——TCP/IP通信案例:访问Internet上的Web服务器...

    第四章      TCP/IP通信案例:访问Internet上的Web服务器 HTTP协议是工作在应用层上的协议,其应用十分广泛,而在进行通信的过程中,经常使用HTTP代理服务器.HTTP代理服务器主 ...

  7. java安全——类加载器+字节码校验+安全管理器与访问权限

    [0]README 0.1)本文文字描述转自 core java volume 2,旨在学习 java安全 的相关知识: [1]类加载器 1)java 技术提供了以下3种确保安全的机制(mechani ...

  8. java代码安全检测机制,下列选项中,属于Java语言的代码安全检测机制的一项是______。A.动态链接B.访问权限的检查C.通过接...

    下列选项中,属于Java语言的代码安全检测机制的一项是______.A.动态链接B.访问权限的检查C.通过接 更多相关问题 Schema类型定义中当需要基于内置的基本数据类型定义一个新的数据类型时,用 ...

  9. linux最上层目录是什么,Linux基础知识之--目录组成结构,当前目录及上层目录表示方法,目录访问权限...

    Linux系统中目录组成结构 • Linux系统的目录组成类拟一个倒置的树型结构,它以一个名为 根("/")的目录开始向下延伸 • 它不同与其它操作系统.例如windows,在 w ...

最新文章

  1. validateJarFile jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/
  2. php字符串截取自​定义函数
  3. 系统运维:收集6款经典的服务器监控工具
  4. 关于mysql的一些时间格式和字符的问题
  5. 带你全面了解Http和Https
  6. int指针初始化_C++:变量,指针,引用const,extern,using,typedef,decltype关键字
  7. django-演练-英雄-作品-对应关系
  8. interface接口_Java程序设计--接口interface(笔记)
  9. 拓端tecdat|R语言GARCH-DCC模型和DCC(MVT)建模估计
  10. visio 2020 最新版安装过程及注意事项
  11. 三行条形码打印样式设计 html,条形码生成及打印(JsBarcode)
  12. 如何在Java项目中定义并调用自己编写的native方法?
  13. [流媒体服务器搭建] EasyDarwin服务器搭建及客户端推流完整示例
  14. CN 国家顶级域名(摘自网络)
  15. 2021 OpenCV人工智能竞赛优秀项目团队介绍集锦(六)
  16. python逆时针旋转矩阵_由外向内顺时针逆时针旋转矩阵
  17. 大神带你秒懂Modbus通信协议
  18. ASP.NET Core中如何显示[PII is hidden]的隐藏信息
  19. AndLua加密解密
  20. Notepad++查看二进制文件——HexEditor插件

热门文章

  1. vue sync用法
  2. 8. Python 数据类型
  3. Spark Streaming 实现思路与模块概述
  4. spring mvc绑定对象String转Date解决入参不能是Date的问题
  5. 对Visual Studio 示例:Fitch and Mather 7.0的研究初步
  6. 单位脉冲信号与单位冲激信号的区别
  7. mariadb设置root初始密码
  8. pandas输出csv某一列的数据
  9. Found option without preceding group
  10. some understanding of《Inferring Decision Trees Using the Minimum Description Length Principle*》