实现一个很简单的功能,两个动态页面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好多强人,他们对浏览器的理解程度肯定比我这个野路子出身的高深的多,就发了个邮件转给他们请他们协助解决,前端就交给专业人士处理去吧。
开发过程中使用外部web服务的时候,通常我们可以通过工具如VS直接“添加web引用”,还有一种比较常用的方式是通过VS提供的命令行工具直接生成web服务代理类。
通过命令行工具的方式被很多开发人员采用,笔者也不例外。通常生成代理类的命令行格式如下:
wsdl /language:cs /out:MyService.cs /namespace:Myspace url或本地地址
但是实际开发的时候发现依赖的外部服务比较多,按照上面的方法你不得不按照各个web服务URL一个一个生成本地代理类,很明显,你会觉得这样做比较费事。好在我们知道了这个命令行的几个参数的含义,可以使用codedom技术动态下载编译生成代理类,然后通过反射调用服务方法。主要实现封装成如下示例代码:
class="cnblogs_code_Collapse">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