Hello World
Spiga

让UpdatePanel支持文件上传(4):数据传输与解析机制

2007-04-11 15:02 by 老赵, 7175 visits

现在就要开始整个项目中最有技巧的部分了。如果我们的组件需要在多种浏览器中正常的运行,我们必须好好考虑一下发送和解析数据的方式。如果我们把这部分的机制完全交给ASP.NET AJAX原有的行为来执行,则会遇到问题。下面的代码片断就是IE 7和FireFox在收到服务器端的数据之后,iframe中的DOM结构:

<html><head></head><body><pre>33|updatePanel|ctl00_Main_UpdatePanel1|...</pre></body></html>

很显然,这段代码的意图是为了在页面中直接显示服务器端发送过来的数据。在这种情况下,我们就可以通过“<pre />”元素的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 = 
            "<script type='text/javascript' language='javascript'>window.__f__=function(){/*";
        string scriptEnd = "*/}</script>";

        response.Write(begin ? scriptBegin : scriptEnd);
    }
}

IE 6和IE 7会将使用<script />来包含的文本作为一段脚本代码来处理。我们这里在真实的数据两边加上了脚本定义的内容,使它成为了客户端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

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

15 条回复

  1. endision
    *.*.*.*
    链接

    endision 2007-04-11 15:34:00

    原来是沙发

  2. 老夫子系
    *.*.*.*
    链接

    老夫子系 2007-04-11 16:13:00

    好文章!喜欢这种深入分析的文章。

  3. bill[未注册用户]
    *.*.*.*
    链接

    bill[未注册用户] 2007-04-11 16:55:00

    很巧妙的解決方法

  4. 老赵
    admin
    链接

    老赵 2007-04-11 17:12:00

    这个机制目前没有发现有什么问题,不知道大家能不能想出反例,或者说更好的解决方案。:)

  5. Axu[未注册用户]
    *.*.*.*
    链接

    Axu[未注册用户] 2007-04-11 19:42:00

    这篇文章的中文版出了好久,呵呵。

  6. SNS[未注册用户]
    *.*.*.*
    链接

    SNS[未注册用户] 2007-04-11 21:13:00

    会有这个错误
    Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.
    Details: Error parsing near '<script type=text/j'.

  7. MK2
    *.*.*.*
    链接

    MK2 2007-04-11 21:51:00

    请问老赵是否写了Debug版或Release版两种脚本的? 抑或是用什么软件转换成Release版的?

    还是支持中文优先......因为你的标题上也写了:“让外国人看中国人写的技术书籍和文章”........ 要先让他们看中文, 就像我们看他们的文章一样, 要先看E文....

  8. 老赵
    admin
    链接

    老赵 2007-04-12 00:31:00

    @MK2
    先写中文再写英文比反一反要累啊。:)

  9. 老赵
    admin
    链接

    老赵 2007-04-12 00:31:00

    @SNS
    请问您用的是什么浏览器,什么样的代码遇到这个错误的呢?能不能把代码发到jeffz@live.com呢?:)

  10. SNS[未注册用户]
    *.*.*.*
    链接

    SNS[未注册用户] 2007-04-12 16:56:00

    to Jeffrey Zhao:
    我是在页面中使用了 Telerik 的 RadTreeView 控件发生这个错误的,但是在我建立的测试项目中发生的是500错
    Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 500
    测试项目我已经发给你了

  11. 老赵
    admin
    链接

    老赵 2007-04-12 17:11:00

    @SNS
    谢谢您的支持,我看一下:)

  12. 老赵
    admin
    链接

    老赵 2007-04-12 21:50:00

    @SNS
    谢谢您的支持,我知道问题所在了。我少想了一种情况,谢谢!:)

  13. SNS[未注册用户]
    *.*.*.*
    链接

    SNS[未注册用户] 2007-04-13 00:15:00

    期待你的修正版 :)

  14. 崽崽[未注册用户]
    *.*.*.*
    链接

    崽崽[未注册用户] 2007-09-27 15:44:00

    会有这个错误
    Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.
    Details: Error parsing near '<script type=text/j'.

    这个错误怎么解决,老赵,期待ing。。。
    谢谢!!

  15. 需要帮助[未注册用户]
    *.*.*.*
    链接

    需要帮助[未注册用户] 2007-12-24 09:42:00

    怎么上传第一次可以,,第2次就报那个500的错误地??就算上传时传个文件格式不正确的,,也是第一次可以,第2次也不行了,也是500的错误

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我