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