實現一個很簡單的功能,兩個動態頁面A和B,從A頁面導航至B頁面,導航通過JS函數控制,具體寫法就是window.location.href=xxx,然后點擊瀏覽器上的回退箭頭,可以從B頁面回退到A頁面。
各主流瀏覽器工作得非常好,IE6也很配合,好,很好,非常好。
但是,可是,可但是,在某些點擊順序變化的情況下,點擊回退箭頭,安全站點下的Safari總是強制彈出對話框。當然鑒於情況較為罕見,測試也沒有計較,至少不能算不兼容。
到這里這個功能就算完成了?可能,也許,maybe…先上生產經受一下用戶考驗再說。
果然,在天朝總有些例外的情況會發生,別忘了我們有一堆中國特色的國產奇葩瀏覽器,而這次例外的是360安全瀏覽器和360極速瀏覽器。
用戶反饋360瀏覽器回退偶爾有點問題,具體現象就是偶爾(我再次說了是偶爾)會發生點擊360瀏覽器回退箭頭直接有一個刷新前一頁面(A頁面)的動作,因為刷新導致A頁面請求參數可能不合法而報錯。
但是實際上我們所理解的回退功能是瀏覽器的獨特的頁面“記憶”功能,根本不需要頁面再次刷新,所以我就順理成章地想到要解決回退刷新頁面的問題。
一開始懷疑是A頁面的某個ajax異步調用造成的問題,后來抓包跟蹤發現不是。
然后分析分析再分析排查排查再排查…
又過了很久終於想到可能和A頁面的JS導航函數有關,就查了一下location.href,發現還有個document.location.href。然后我就學到window.location.href和document.location.href的主要區別:window.location.href對單一窗口單一文檔(document)導航通常沒有問題,但如果一個窗口中包含多個文檔(比如頁面當中嵌入iframe)在某些瀏覽器下就會導致奇怪的現象,例如導航失敗,還有就是本文所寫的瀏覽器回退功能不太正常。而通常情況下document.location.href和window.location.href功能一樣,但在多文檔窗口下它更適用一些。
看上面分析的區別,簡直讓我如夢方醒大徹大悟忍不住一陣激動。就嘗試着把導航方法換成document.location.href,本地測試問題竟然沒有重現,清理瀏覽器緩存重試果然確實沒有重現。
再去排查A頁面,果然有一個隱藏的曾經起過作用但是現在已被棄用的iframe,為了保險起見,又把A頁面的iframe也去掉。
接着再發布再跑測試自己的電腦上果然正常了,非常好,很好,好。
此時我發自內心由衷欽佩自己的勤勉和嚴謹哈哈哈哈哈哈。
到這里你以為問題徹底解決了?圖樣圖森破。
測試人員報告說問題依舊,清理瀏覽器緩存也不行,后來發布生產再驗證果然問題依舊,而且測試主管順帶又提了個問題,iphone上chrome瀏覽器回退報一樣的錯誤,好的,報個BUG先。
個人歷史悠久的實踐經驗表明,分析解決瀏覽器兼容性這樣的BUG簡直就是開發人員的噩夢。除了技術方面,你還需要考慮外部環境如客戶端設置、網絡狀況等等。針對本文所描述的瀏覽器回退功能的缺陷,至少我查了一堆資料就沒有幾個說法是可行的。有人提議重寫瀏覽器的回退事件,我覺得也不合理,至少有點簡單問題復雜化處理了,還是覺得document.loaction.href那個是至今查到的看上去理論上是最靠譜的,但是,你也許已經知道,“在理論上,理論和實踐之間沒有什么差別;在實踐中,二者果然截然不同”。
在一番痛苦掙扎之后,想起我的直接領導以前對我說過的一句話,別一味埋頭做事,有資源要學會充分利用,我就做了一個重大而明智的決定。聽說我司UED好多強人,他們對瀏覽器的理解程度肯定比我這個野路子出身的高深的多,就發了個郵件轉給他們請他們協助解決,前端就交給專業人士處理去吧。
附:動態調用webservice
開發過程中使用外部web服務的時候,通常我們可以通過工具如VS直接“添加web引用”,還有一種比較常用的方式是通過VS提供的命令行工具直接生成web服務代理類。
通過命令行工具的方式被很多開發人員采用,筆者也不例外。通常生成代理類的命令行格式如下:
wsdl /language:cs /out:MyService.cs /namespace:Myspace url或本地地址
但是實際開發的時候發現依賴的外部服務比較多,按照上面的方法你不得不按照各個web服務URL一個一個生成本地代理類,很明顯,你會覺得這樣做比較費事。好在我們知道了這個命令行的幾個參數的含義,可以使用codedom技術動態下載編譯生成代理類,然后通過反射調用服務方法。主要實現封裝成如下示例代碼:

DynamicInvokeWebService
通過上面的代碼,我們可以分析得出,動態生成webservice的過程主要包括幾步:
1、從目標URL下載WSDL數據
2、生成客戶端代理類代碼
3、並編譯代理類
4、生成代理實例,並利用反射調用方法。
調用的形式如下:
var affectNum = WebServiceUtil.DynamicInvokeWebService("webservice url", "OrderService", "SetFinishedByOrderId", 123456, true); if (affectNum != null) { Console.WriteLine((bool)affectNum);//設置訂單狀態是否完成 }
可以想象,這樣動態構造web服務並進行調用肯定會有一些性能損失,如果是后台應用系統,對性能要求不是特別高的情況下倒是不妨一試。
最后需要注意的一點,就是因為web服務是動態調用的,所以除了c#的基元類型如int、string、object等等,自定義類型是無法通過上述方法轉換回來的,有心的你不妨動手一試。
參考:
http://www.codeproject.com/Articles/18950/Dynamic-Discovery-and-Invocation-of-Web-Services
http://www.codeproject.com/Articles/14586/Invoking-a-Web-Service-Without-Web-Reference
http://www.codeproject.com/Articles/591608/GolabiplusWebserviceplusDynamicplusInvoker
http://www.codeproject.com/Articles/94043/SOAP-Web-Services-Create-Once-Consume-Everywhere