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

这是松结对编程的第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这些都是接近一年半年前产生的,那时候完全没想到会与现在要做的权限控制相关。只能说,如果做对了事情,回报是迟早的。

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
L组件填图问题 1.问题描述 设B是一个n×n棋盘,n=2k,(k=1,2,3,…)。用分治法设计一个算法,使得:用若干个L条块可以覆盖住B的除一个特殊方格外的所有方格。其中,一个L条块可以覆盖3个方格。且任意两个L条块不能重叠覆盖棋盘。 例如:如果n=2,则存在4个方格,其中,除一个方格外,其余3个方格可被一L条块覆盖;当n=4时,则存在16个方格,其中,除一个方格外,其余15个方格被5个L条块覆盖。 2. 具体要求 输入一个正整数n,表示棋盘的大小是n*n的。输出一个被L条块覆盖的n*n棋盘。该棋盘除一个方格外,其余各方格都被L条块覆盖住。为区别出各个方格是被哪个L条块所覆盖,每个L条块用不同的数字或颜色、标记表示。 3. 测试数据(仅作为参考) 输入:8 输出:A 2 3 3 7 7 8 8 2 2 1 3 7 6 6 8 4 1 1 5 9 9 6 10 4 4 5 5 0 9 10 10 12 12 13 0 0 17 18 18 12 11 13 13 17 17 16 18 14 11 11 15 19 16 16 20 14 14 15 15 19 19 20 20 4. 设计与实现的提示 对2k×2k的棋盘可以划分成若干块,每块棋盘是原棋盘的子棋盘或者可以转化成原棋盘的子棋盘。 注意:特殊方格的位置是任意的。而且,L条块是可以旋转放置的。 为了区分出棋盘上的方格被不同的L条块所覆盖,每个L条块可以用不同的数字、颜色等来标记区分。 5. 扩展内容 可以采用可视化界面来表示各L条块,显示其覆盖棋盘的情况。 经典的递归问题, 这是我的大代码, 只是本人很懒, 不想再优化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值