现在就要开始整个项目中最有技巧的部分了。如果我们的组件需要在多种浏览器中正常的运行,我们必须好好考虑一下发送和解析数据的方式。如果我们把这部分的机制完全交给ASP.NET AJAX原有的行为来执行,则会遇到问题。下面的代码片断就是IE 7和FireFox在收到服务器端的数据之后,iframe中的DOM结构:
<html><head>head><body><pre>33|updatePanel|ctl00_Main_UpdatePanel1|...pre>body>html>
很显然,这段代码的意图是为了在页面中直接显示服务器端发送过来的数据。在这种情况下,我们就可以通过“
”元素的innertText属性(IE 7)或者textContent属性(FireFox)来直接获得这段文字。不幸的是,IE6的行为非常奇怪,与前两者可谓大相径庭。IE 6会把这段文字按照XML来解析,接着很自然的显示出错误信息,告诉我们这段文本不是一个有效的XML文档。这非常不合理,因为Response的“Content-Type”是“text/plain”而不是“text/xml”。这是我们要兼容多个浏览器时最头疼的情况。还记得我们在向客户段输出真实的数据前后都调用了WriteScriptBlock方法吗?下面就是这个方法的实现:
internal static class AjaxFileUploadUtility { internal static void WriteScriptBlock(HttpResponse response, bool begin) { string scriptBegin = ""; response.Write(begin ? scriptBegin : scriptEnd); } }
IE 6和IE 7会将使用来包含的文本作为一段脚本代码来处理。我们这里在真实的数据两边加上了脚本定义的内容,使它成为了客户端iframe中“__f__”方法的一段注释,因此我们可以通过调用这个方法的toString函数来获得这个方法的文本内容。请注意在RenderPageCallback方法中,我们把文本进行了编码,将“*/”替换为“*//*”,然后再将其发送到客户端,这么做的目的是使这段文本能够成为合法的JavaScirpt代码。
StringBuilder sb = new StringBuilder(); HtmlTextWriter innerWriter = new HtmlTextWriter(new StringWriter(sb)); renderPageCallbackMethodInfo.Invoke( this.PageRequestManager, new object[] { innerWriter, pageControl }); writer.Write(sb.Replace("*/", "*//*").ToString());
等一下,我们在这里把异步刷新运行正常时输出的文本进行了编码,但是我们在异常情况下的输出并没有这么做,不是吗?没错。因为在异常状况下,错误信息会通过Response的Write方法直接输出(请看PageRequestManager类的OnPageError方法),因此我们无法向前面的代码那样获得它输出的结果。我们现在只能希望错误信息中不要出现“*/”这样的字符串吧(当然,我们可以使用反射机制来重写整个逻辑,但是这样做实在比较复杂)。
下面,我们就要在客户端的_iframeLoadComplete方法中重新获取这段文本了:
_iframeLoadComplete : function() { //... try { var f = iframe.contentWindow.__f__; var responseData = f ? this._parseScriptText(f.toString()) : this._parsePreNode(iframe.contentWindow.document.body.firstChild); if (responseData.indexOf("\r\n") < 0 && responseData.indexOf("\n") > 0) { responseData = responseData.replace(/\n/g, "\r\n"); } this._responseData = responseData; this._statusCode = 200; this._responseAvailable = true; } catch (e) { this._statusCode = 500; this._responseAvailable = false; } // ... }, _parseScriptText : function(scriptText) { var indexBegin = scriptText.indexOf("/*") + 2; var indexEnd = scriptText.lastIndexOf("*/"); var encodedText = scriptText.substring(indexBegin, indexEnd); return encodedText.replace(/\*\/\/\*/g, "*/"); },
我们在这里将判断iframe的window对象中是否存在“__f__”方法,而不是直接判断浏览器的类型来决定下面要做的事情,因为这样可以带来更多的浏览器兼容性。
FireFox的行为则完全不是这样的,它依旧使用我们一开始提到的那种DOM结构,把从服务器端得到的文本显示在iframe中,这种做法比较合理,因为Response的Content-Type为“text-plain”。因此,我们会使用另一种方法来得到这段文本:
_parsePreNode : function(preNode) { if (preNode.tagName.toUpperCase() !== "PRE") throw new Error(); return this._parseScriptText(preNode.textContent || preNode.innerText); },
请注意,“_iframeLoadComplete”方法中还有几行非常重要的代码:
if (responseData.indexOf("\r\n") < 0 && responseData.indexOf("\n") > 0) { responseData = responseData.replace(/\n/g, "\r\n"); }
由于从服务器端得到的脚本将会被分割为多个部分,每个部分的格式为“length|type|id|content”,因此字符串的长度是在解析文本时非常重要的属性。因此,我们将会把所有的“\r”替换成“\r\n”,以此保持内容和长度的一致,否则解析过程将会失败。而且事实上,这样的替换只会出现在FireFox中。(未完待续)
点击这里下载整个项目
English Version