本文是對工作中的項(xiàng)目進(jìn)行代碼優(yōu)化(完善登陸驗(yàn)證的AOP切面編程)時(shí),所遇到的各種解決方案思考過程。
項(xiàng)目背景:由ashx+nvelocity構(gòu)建的簡單B/S問卷系統(tǒng),現(xiàn)需要優(yōu)化登錄驗(yàn)證環(huán)節(jié)(時(shí)隔若干個(gè)月在回顧代碼果然是一個(gè)痛苦的過程~)
nvelocity是velocity框架針對.net的版本,核心是拼html字符串后返回客戶端,與MVC的前后端代碼隔離有異曲同工之妙。加之一般處理程序ashx不需要像asp.net那樣走生成控件樹的過程,執(zhí)行上更是省時(shí)省力。故簡單系統(tǒng)用ashx+nvelocity的形式構(gòu)建筆者個(gè)人還是比較推薦的。如果那么在意訪問地址(如www.abc.com/news/index.ashx?id=234)中的ashx后綴不好看,完全可以通過模塊(HttpModule)來實(shí)現(xiàn)url重寫。
本文討論的是:如何在ashx中體現(xiàn)AOP切面編程思想?
(1)回顧asp.net,所有頁面繼承自Page類,可通過Page的子類來實(shí)現(xiàn)AOP。原來是:Default : Page,切面插入后是:Default : LoginCheckPage,LoginCheckPage : Page。如此便能在LoginCheckPage類中編寫登錄驗(yàn)證的代碼,且能實(shí)現(xiàn)所有需要驗(yàn)證頁面的有效解耦——解耦是相對于專門寫一個(gè)LoginCheck類,并在各個(gè)Default頁面做驗(yàn)證(如LoginCheck.Check(context))而言,更利于修改與拓展。
(2)回顧MVC,可以依樣畫葫蘆像上述asp.net那樣,原來是:HomeController:Controller,切面插入后是:HomeController:LoginCheckController,LoginCheckController:Controller。除此之外,還能利用類/方法頭上的特性標(biāo)簽來做AOP。
在asp.net與MVC中的AOP體現(xiàn)還有許多做法,此處拋磚引玉、僅為比對ashx的AOP做思考:上述方法在ashx中能行得通嗎? 能!但要做些微調(diào):
(獨(dú)寫一個(gè)LoginCheck類,然后在每個(gè)需要驗(yàn)證的ashx.PR()中加上LoginCheck.Check(context)實(shí)在不是長久之計(jì),故本文就不另說了)
第一種嘗試:(沒錯(cuò),本文最后一次才嘗試成功,不過寫出嘗試過程也是為了將自己所走的彎路做下記錄,且希望能給讀者更多思考的提示,感謝堅(jiān)持讀完三種嘗試的朋友。)
利用HttpModule。類似url重寫那樣,url重寫不都是每次請求一來就做處理嗎,那Module應(yīng)該也能做登陸吧——兩者差異:普通的url重寫不涉及客戶端隔離、不考慮請求的資源,登錄驗(yàn)證要做客戶端隔離(cookie)、要考慮請求的資源(并不是所有資源都不給訪問,有的是游客級別就行的)。
對于要考慮請求資源的差異,如果惡心一點(diǎn),可以在代碼中寫死(可優(yōu)化成在webcofig、其他配置文件、數(shù)據(jù)庫存儲(chǔ))來做差異化處理——以正則表達(dá)式匹配請求地址,用來隔離需要驗(yàn)證登陸的請求與不需要驗(yàn)證的請求。
對于客戶端隔離,能否直接在Module中用session?首先要使HttpModule繼承自IReadonlySessionState/IRequiresSessionState接口(HttpHandler也是如此),以便在走管道的時(shí)候能被.net認(rèn)出來你這個(gè)Module想用session。注冊到BeginRequest事件。別忘了還要注冊到webconfig。一切就緒,調(diào)試,報(bào)錯(cuò)——Httpapplication中的Session屬性報(bào)錯(cuò),未將對象引用設(shè)置到對象實(shí)例。是不是注冊的事件錯(cuò)了?我查了一遍HttpApplication管道中的19個(gè)事件,最佳的切入點(diǎn)在第10-11個(gè)事件之間,也就是+=PostAcquireRequest,才能在獲取Session之后、在執(zhí)行ashx之前做登陸驗(yàn)證。
然而并沒有什么X用……依舊未將對象引用設(shè)置到對象實(shí)例。怎么還是沒有呢,奇了怪了。
又是一邊各種查,查到一句話說得好:Module是應(yīng)用程序級的事兒,是過濾作用,而Session是頁面級的事兒,是要根據(jù)發(fā)來的請求做不同的處理,故在Module中用Session本就不是最佳方案。故放棄Module這條彎路。
第二種嘗試:
自定義繼承自IHttpHandler的ashx。原來:Index:IHttpHandler,優(yōu)化后:Index:LoginCheckHandler,LoginCheckHandler:IHttpHandler。學(xué)的上述asp.net與mvc中的插入到繼承樹的方法。但調(diào)試結(jié)果是根本不走Index的ProcessRequest(),直接走完LoginCheckHandler.ProcessRequest()就返回了,客戶端就是空白一片。究其原因:實(shí)現(xiàn)IHttpHandler的一般處理程序(無論是Index,還是LoginCheckHandler),都只會(huì)執(zhí)行一次ProcessRequest()。
第三種嘗試:
在第二種的基礎(chǔ)上修改為:LoginCheckHandler中的ProcessRequest()改為virtual,并在Index子類中override重寫,并在Index.ProcessRequest()中調(diào)用base.ProcessRequest(context)。執(zhí)行的時(shí)候,程序會(huì)因?yàn)榭吹給verride而忽略父類的PR方法,而Index子類中的base.PR()又要求程序先走父類的PR方法,且結(jié)合Response.Redirect()的立即輸出特性(先Flush,在End),可以使得不滿足登錄驗(yàn)證條件的請求被擋在門外。小功告成!
麻煩的是,要修改子類為override,且在子類中存在base.PR()代碼(也只比簡單粗暴的調(diào)用LoginCheck.Check(context)來驗(yàn)證減少了一些些耦合度),那么還有更好的AOP方法嗎?望各位大牛看官提點(diǎn)。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注