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