这是松结对编程的第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(中)

    这是松结对编程的第22篇(专栏目录). 接前文 业务代码 比较长,基本上就是看被注释隔开的三大段,先显示状态群筛选链接,然后是单个状态筛选,然后是显示下拉框的当前选中项,最后显示下拉框. public ...

  2. 3.GO-项目结构、包访问权限、闭包和值传递引用传递

    3.1.goland中项目结构 (1)在goland中创建标准Go项目 (2)goland配置 创建项目Learn-Go file-settings-go-GOPATH-添加 在项目目录下创建src目 ...

  3. go 基础 结构体 接口 访问权限

    package School type SchoolModel struct { Name string Address string StudentCount int Is985 bool } ty ...

  4. 初读"Thinking in Java"读书笔记之第六章 --- 访问权限控制

    包:库单元 包内包含有一组类,他们在单一的名字空间下被组织在一起. 通过import ***.***.*可以将某个包下的所有类导入到当前文件中. 每个Java源文件最多只能有一个public类,且名称 ...

  5. Linux中ls -l(ll)返回结果中的文件访问权限-rw-r--rw-

    linux文件访问权限(像rw-r--rw-是什么意思)   Linux的文件访问权限分为 读.写.执行三种 r:可读(4) w:可写(2)对目录来说则可新建文件 x:可执行(1)对目录来说则可进入该 ...

  6. SYS_数据访问权限Operation Unit和Ledger的访问设定(案例)

    2014-06-01 Created By BaoXinjian

  7. [apue] linux 文件访问权限那些事儿

    前言 说到 linux 上的文件权限,其实我们在说两个实体,一是文件,二是进程.一个进程能不能访问一个文件,其实由三部分内容决定: 文件的所有者.所在的组: 文件对所有者.组用户.其它用户设置的权限访 ...

  8. CentOS 6.5系统上安装SVN服务器端的方法及目录访问权限配置(转总结)

    SVN其实就是Subversion,分为服务器端和客户端.之前在网上搜了很多方法,都有各种问题,经过自己搜集整理以及实际尝试,总算有个比较靠谱的方法.本文主要介绍CentOS 6.5系统上安装SVN服 ...

  9. Unix/Linux文件类型及访问权限

    在Linux系统中,有7种文件类型. 普通文件 (regular file) 目录文件 (directory) 链接文件 (symbolic link) 管道文件 (FIFO) 套接字文件 (sock ...

随机推荐

  1. iOS面试题05-父子控制器、内存管理

    内存管理.父子控制器面试题 1.建立父子关系控制器有什么用 回答:1>监听屏幕选中 2>如果想拿到你当前的很小的一个控制器所在的导航控制器必须要跟外面比较大的控制器建立父子关系,才能一层一 ...

  2. 使用 IObjectSafety 标记 ATL 控件初始化的安全

    MSDN原文.这里我将代码使用到了BHO里面,运行调试没问题.拿来分享一下 概要 您可以使用 IObjectSafetyImpl 的默认实现来标记为可安全执行脚本的控件.在许多情况下,您需要将标记为可 ...

  3. BZOJ 1005: [HNOI2008]明明的烦恼( 组合数学 + 高精度 )

    首先要知道一种prufer数列的东西...一个prufer数列和一颗树对应..然后树上一个点的度数-1是这个点在prufer数列中出现次数..这样就转成一个排列组合的问题了.算个可重集的排列数和组合数 ...

  4. UNIX/Linux-进程控制(实例入门篇)

    UNIX进程   进程标识符 要想对进程控制,必须得获取进程的标识.每个进程都有一个非负整数表示的唯一进程ID,虽然是唯一的,但是进程ID可以重用.当一个进程终止后,其进程ID就可以再次使用了. 系统 ...

  5. PHP新闻系统开发流程

    PHP新闻系统开发流程一.系统总体设计 (一)系统功能描述和功能模块划分 (二)系统流程分析 (三)系统所用文件二.数据库设计 (一)创建数据库 (二)设计表结构三.新闻发布模块开发 (一)新闻首页 ...

  6. bmfont制作数字

    http://blog.csdn.net/z104207/article/details/20136401

  7. verilog中always块延时总结

    在上一篇博文中 verilog中连续性赋值中的延时中对assign的延时做了讨论,现在对always块中的延时做一个讨论. 观测下面的程序,@0时刻,输入的数据分别是0x13,0x14 . @2时刻, ...

  8. ASP.NET jQuery 随笔 使用jQuery UI的Autocomplete方法实现文本框的自动搜索填充功能

    首先当然是去下载JQuery UI ,这里这里是下载地址http://jqueryui.com/ 第一步是点击这里 第二步选择你想要下载的主题进行下载 我这里是选择的cupertino主题包 点击圆圈 ...

  9. 帝国cms7.0,列表模板调用不支持附表字段

    帝国cms在制作列表模板时,是不支持一些字段的调用的,原因是因为有些字段所在的位置为附表,本段详细向你介绍 帝国如何调用副表字段 我们可在 系统---管理数据表---管理字段中查看 如果我们需要调用附 ...

  10. HDU 2962 Trucking

    题目大意:给定无向图,每一条路上都有限重,求能到达目的地的最大限重,同时算出其最短路. 题解:由于有限重,所以二分检索,将二分的值代入最短路中,不断保存和更新即可. #include <cstd ...