L型代码结构案例:Link访问权限(上)
這是松結對編程的第20篇(專欄目錄)。
本文探討Link訪問權限的最佳實現(xiàn)方法,力求外觀干凈且封裝良好。
這些代碼將位于L型代碼結構(參見松結對編程系列中的定義)的下層,調(diào)用者無需理解其原理。
?
順便說一下,我們做的是管理信息系統(tǒng),和互聯(lián)網(wǎng)社區(qū)軟件的一個區(qū)別是很多鏈接都是需要特定權限才能訪問的,有些權限也不是非常直觀能猜到應該具備何種條件才能訪問,另外一些權限還會經(jīng)常改動。因此一個容易使用、容易維護、不容易出錯的權限機制尤為重要。
無權訪問時該顯示什么
在說實現(xiàn)方法之前,先說說如果鏈接訪問條件不滿足,應該顯示什么。
實踐發(fā)現(xiàn)顯示文字不好,因為文字(這里應該是一段解釋為何不能訪問的文字)肯定比鏈接長,顯示空間不好。
什么都不顯示如何?也不太好,用戶可能會誤以為沒有這樣一個功能,或鏈接不在這個頁面上而去其他頁面尋找。
最后現(xiàn)在是顯示一個灰色的鏈接,懸停時解釋需要什么條件才能訪問這個鏈接(這樣用戶如果想操作它但卻沒有權限,就會知道該怎么辦)。
顯示方法
方法1:散裝代碼
?
一般而言,如果要限制一個Link的訪問權限,都是這樣的:
if (condiction) {@link } else {@text //灰色代碼,或干脆什么都不顯示。 }?
這樣的最大壞處是,如果一個頁面上有很多鏈接(比如導航頁面),那么遍地都是if-else,眼花繚亂。
而且一旦權限修改了,就要到處修改所有可能引用過的地方。
方法2:封裝Link
下面是我們原來封裝的Link,里邊有兩個參數(shù): displayAsLink,即何種條件下應該顯示為Link,否則將顯示為灰色文字。比如product.IsProductManager()是問當前用戶(括號內(nèi)無值時自動采用當前用戶)是否是產(chǎn)品經(jīng)理。是,才顯示鏈接。 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: "可拖拽和執(zhí)行所有操作,速度較慢",displayAsLink: product.IsProductManager(),grayTextTitle: "需要是此產(chǎn)品的產(chǎn)品經(jīng)理才能操作。")@MFCUI.ImageLink("詳情樹","/ProductManagement/StoryTrees/DetailsTree?" + Request.QueryString,displayAsBoldTextOnPage: this, title: "適合快速查看所有故事的詳情")@MFCUI.ImageLink("編輯樹","/ProductManagement/StoryTrees/EditTree?" + Request.QueryString,displayAsBoldTextOnPage: this, title: "適合連續(xù)編輯多個故事的數(shù)據(jù)",displayAsLink: product.IsProductManager(),grayTextTitle: "需要是此產(chǎn)品的產(chǎn)品經(jīng)理才能操作。") 這個方法解決了前面提到的if-else滿天飛的問題,但是要解決缺陷更改造成的代碼改動還不行。此外,一些解釋性的語言如“需要時此產(chǎn)品的產(chǎn)品經(jīng)理才能操作”也存在多處文字的統(tǒng)一問題;甚至即使在同一地方,如果displayAsLink修改了條件,而grayTextTitle沒有相應修改,會造成用戶的錯誤理解。
方法3:封裝模型
其實上面四句話中,能訪問或不能訪問,都是關于product的寫操作權限的,那么如果用product自己來判斷,那么代碼就會集中在product內(nèi)部,由專業(yè)維護此類的人員來確定權限和解釋。 這個是現(xiàn)在為止最佳的選擇。 當前View調(diào)用處的代碼如下: @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: "可拖拽和執(zhí)行所有操作,速度較慢",displayAsLink: IsProductManager(),grayTextTitle: "需要是此產(chǎn)品的產(chǎn)品經(jīng)理才能操作。");}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: "適合連續(xù)編輯多個故事的數(shù)據(jù)",displayAsLink: IsProductManager(),grayTextTitle: "需要是此產(chǎn)品的產(chǎn)品經(jīng)理才能操作。");}}剛才查找替換了一下,每個鏈接都出現(xiàn)過6次。通過改寫后,原來有很多可能導致不一致的參數(shù)的調(diào)用都變成一個只傳輸(this)的參數(shù)了,未來維護會簡單地多。
下面是一個具體的實現(xiàn)效果:
方法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("只有此產(chǎn)品的產(chǎn)品經(jīng)理才可以操作。");return OperateTreeView(...);} 可惜有這么幾個限制: 1. 只提供Users(常量的用戶名,基本沒什么用)和Roles(上例中的)兩種。 比如上例中“此產(chǎn)品的產(chǎn)品經(jīng)理”,就只能編碼實現(xiàn)。 2. (似乎)沒有地方查看某個Action具體訪問權限是什么 也就是說,不能在別處生成指向此Action的鏈接時,自動根據(jù)所限定的Users或Roles來自動決定顯示為鏈接或文字。 重寫Authorize可以根據(jù)自己的條件來限制訪問,唯一的問題是“自己的條件”如果太多,那么會很復雜。還好,到現(xiàn)在為止火星人中就用了兩種限制: 1. 某人是某個角色。 比如SiteUsersController只有Admin才可以訪問,這個是站點的用戶管理功能。 2. 某人是某物的某個角色。 現(xiàn)在火星人中,“某物”包括產(chǎn)品(的產(chǎn)品經(jīng)理)、團隊(的項目經(jīng)理、項目助理經(jīng)理即項目經(jīng)理不在時應急用的代理人、項目組員)、用戶故事(的負責人、當前負責人、創(chuàng)建者)、缺陷(的創(chuàng)建人、當前負責人)……,未來還有部門(的經(jīng)理、部門助理經(jīng)理、部門人員)……這些。
這些雖然聽起來很多,但是還好之前為了存儲問題,產(chǎn)品、團隊、用戶故事、缺陷……這些都是從基類UDCable(User Defined Column-able,“可被用戶自定義字段的”)派生的,而剛才說的一大堆角色,都是一個個UDCType(User Defined Column Type,“用戶自定義字段類型”),這樣其實所有剛才說的判斷,都是一種,就是問某人的Id是否等于某個UDCable的某個UDCType字段數(shù)值。
現(xiàn)在還沒時間寫這個Authroize(主要是不會寫,呵呵),估計寫好后使用方法如下: [Authorize(Roles = "ProductManager", UDCRoles="rootID, ProductManager")]public ActionResult EditTree(int rootID, string whats, string whattypes){...return OperateTreeView(...);}
UDCRoles="rootID, ProductManager"是說,用url的rootID數(shù)值來找UDCable,然后判斷其"ProductManager"是否等于當前用戶。
用這個屬性后Action中可以減少3行(一共才5行,所以3行很多了),而整個代碼中有很多這樣的三行代碼,估計現(xiàn)在有30處左右,都很容易寫錯造成漏洞。
?
剩下一個問題,@MFCUI.ImageLink怎么知道這些Action的訪問權限呢?
現(xiàn)在的想法是在屬性代碼中用"Area/Controller/Action"作為Key,權限設置(就是“rootID, ProductManager”)作為值做一個靜態(tài)緩存,ImageLink會根據(jù)自己傳入的Url解析出“Area/Controller/Action”并去查找緩存的值,如果找到就根據(jù)權限進行判斷是否顯示為鏈接)。這樣未來只要在Action前面寫好屬性,所有指向它的鏈接都會自動判斷。
因為之前已經(jīng)有很多可用的代碼了(比如解析A/C/T的代碼),所以估計兩者加起來大約有10~15行代碼就能實現(xiàn)。
估計這些代碼兩個月后才會排到足夠優(yōu)先級,寫好了我共享一下。
總結
所有代碼結構中的第一塊積木是最難的,如果不是我們原來有一些復用了,上面這個訪問權限問題解決起來可能需要上百行代碼,很容易將就一下就硬編碼過去了。
如果我們當年所有View里邊的鏈接都是用<a></a>硬編碼的,或用MVC中自帶的Hmtl.Link()寫的,那么我們也沒有勇氣和動力來用“這么復雜”的方式來解決這個訪問權限問題了。但若干時間后,一旦訪問權限變化了,肯定會因為各地的硬編碼而出現(xiàn)無數(shù)問題,那時候就真的亂了。
UDCable和ImageLink這些都是接近一年半年前產(chǎn)生的,那時候完全沒想到會與現(xiàn)在要做的權限控制相關。只能說,如果做對了事情,回報是遲早的。
?
所以應該隨時隨地把可復用的東西總結起來,這樣反而不覺得累,才能不斷在原來的基礎上前進。
?
總結
以上是生活随笔為你收集整理的L型代码结构案例:Link访问权限(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lync 2013 企业版部署 (四)安
- 下一篇: C++字符串之一(字符表示)