当前位置: 首页  >  ASP.NET  >  遭遇浏览器兼容性问题,这次是某些浏览器回退功能不正常

遭遇浏览器兼容性问题,这次是某些浏览器回退功能不正常 TOP

实现一个很简单的功能,两个动态页面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技术动态下载编译生成代理类,然后通过反射调用服务方法。主要实现封装成如下示例代码:

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Net;
using System.Text;
using System.Web.Services.Description;
using System.Xml.Serialization;
using Microsoft.CSharp;

namespace DotNet.Common.Util
{
    public class WebServiceUtil
    {

        private static readonly string protocal = "Soap";

        public static object DynamicInvokeWebService(string url, string className, string methodName, params object[] args)
        {
            try
            {
                //1、获取WSDL  
                var wc = new WebClient();
                var stream = wc.OpenRead(url + "?WSDL");
                var sd = ServiceDescription.Read(stream);
                ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
                sdi.ProtocolName = protocal; // 指定访问协议。  
                sdi.Style = ServiceDescriptionImportStyle.Client; // 生成客户端代理。  
                sdi.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync;
                sdi.AddServiceDescription(sd, null, null);
                var cn = new CodeNamespace("MySpace");

                //2、生成客户端代理类代码  
                CodeCompileUnit ccu = new CodeCompileUnit();
                ccu.Namespaces.Add(cn);
                sdi.Import(cn, ccu);


                //3、设定编译参数  
                var compilerParameters = new CompilerParameters();
                compilerParameters.GenerateExecutable = false;
                compilerParameters.GenerateInMemory = true;
                compilerParameters.ReferencedAssemblies.Add("System.dll");
                compilerParameters.ReferencedAssemblies.Add("System.Data.dll");
                compilerParameters.ReferencedAssemblies.Add("System.XML.dll");
                compilerParameters.ReferencedAssemblies.Add("System.Web.Services.dll");

                var csc = new CSharpCodeProvider();
                var result = csc.CompileAssemblyFromDom(compilerParameters, ccu); //编译代理类  
                if (result.Errors.HasErrors)
                {
                    var sb = new StringBuilder();
                    foreach (var item in result.Errors)
                    {
                        sb.Append(item.ToString());
                        sb.Append(Environment.NewLine);
                    }
                    throw new Exception(sb.ToString());
                }

                //4、生成代理实例,并通过反射调用方法  
                var assembly = result.CompiledAssembly;
                var t = assembly.GetType("MySpace." + className, true, true);
                var obj = Activator.CreateInstance(t);
                var method = t.GetMethod(methodName);
                return method.Invoke(obj, args);
            }
            catch (Exception ex)
            {
                //Logger.Error(ex);
                throw;
            }
        }
    }
}

通过上面的代码,我们可以分析得出,动态生成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

Views:1026   Posted at:2013-11-29
收藏 推荐 打印 | 录入:Admin | 阅读:0