Hello World
Spiga

让UpdatePanel支持文件上传(5):支持页面重定向的HttpModule

2007-04-12 11:51 by 老赵, 8403 visits

我们现在试用一下这个组件。

首先,我们将AjaxUploadHelper控件放置在页面中,紧跟在ScriptManager之后,因为AjaxUploadHelpe需要在第一时间告诉ScriptManager目前正处在一个异步刷新的过程中。

<%@ Register Assembly="AjaxFileUploadHelper" Namespace="Jeffz.Web" TagPrefix="jeffz" %>

//...
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<jeffz:AjaxFileUploadHelper runat="server" ID="AjaxFileUploadHelper1" />
//...

接着,在页面上添加一个UpdatePanel,并在其中放置一个FileUpload控件,一个按钮以及一个Label。为了更容易地看出异步刷新的效果,我们在页面上添加两个时间:

<%= DateTime.Now %>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <%= DateTime.Now %><br />
        <asp:FileUpload ID="FileUpload1" runat="server" />
        <asp:Button ID="Button1" runat="server" Text="Upload" OnClick="Button1_Click" /><br />
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
    </ContentTemplate>
</asp:UpdatePanel>

在Code Behind代码中,我们为Button添加Event handler:

protected void Button1_Click(object sender, EventArgs e)
{
    if (this.FileUpload1.PostedFile != null)
    {
        this.Label1.Text = this.FileUpload1.PostedFile.ContentLength + " bytes";
    }
    else
    {
        this.Label1.Text = "";
    }
}

打开页面,我们可以看到页面中显示了那些控件和两个时间。

1

选择一个文件并点击Upload按钮,我们可以发现只有UpdatePanel内部的时间被改变了,文件大小也显示在了页面上:

2

很震撼吧?但是如果我们改变Code Behind中的代码:

protected void Button1_Click(object sender, EventArgs e)
{
    this.Response.Redirect("AnotherPage.aspx", true);
}

刷新页面,点击按钮,您就会发现……失败了?为什么?

原因如下:在一个“普通”的PostBack时,如果我们在执行了Redirect方法,浏览器将会接受到一个Status Code为302的Response,以及一个跳转目标。接着浏览器就会将用户带去指定的目标页面。当XHR发出的请求得到这样一个Response之后,它将会自动重新请求而不会告诉客户端究竟发生了什么。这时,客户端只能获得目标跳转之后的资源,而并非起初请求的资源。

因此,ASP.NET AJAX提供了一个组件来支持异步PostBack时的跳转。这个组件就是ScriptModule,我们可以在web.config文件中找到它的注册信息。

<system.web>
    <!-- other configurations -->
    <httpModules>
        <add name="ScriptModule" 
	    type="System.Web.Handlers.ScriptModule, System.Web.Extensions, ..."/>
    </httpModules>
    <!-- other configurations -->
</system.web>
<!-- for IIS 7 -->
<system.webServer>
    <!-- other configurations -->
    <modules>
        <add name="ScriptModule" preCondition="integratedMode" 
	    type="System.Web.Handlers.ScriptModule, System.Web.Extensions, ..."/>
    </modules>
    <!-- other configurations -->
</system.webServer>

下面的代码片断就是它解决这个问题的实现:

public class ScriptModule : IHttpModule
{
    protected virtual void Init(HttpApplication context)
    {
        context.PreSendRequestHeaders += new EventHandler(PreSendRequestHeadersHandler);
        // ...
    }

    private void PreSendRequestHeadersHandler(object sender, EventArgs args)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpResponse response = application.Response;

        if (response.StatusCode == 302)
        {
            if (PageRequestManager.IsAsyncPostBackRequest(application.Request.Headers))
            {   
                string redirectLocation = response.RedirectLocation;

                List<HttpCookie> cookies = new List<HttpCookie>(response.Cookies.Count);
                for (int i = 0; i < response.Cookies.Count; i++) {
                    cookies.Add(response.Cookies[i]);
                }

                response.ClearContent();
                response.ClearHeaders();

                for (int i = 0; i < cookies.Count; i++)
                {
                    response.AppendCookie(cookies[i]);
                }

                response.Cache.SetCacheability(HttpCacheability.NoCache);
                response.ContentType = "text/plain";
                PageRequestManager.EncodeString(response.Output, "pageRedirect", 
                    String.Empty, redirectLocation);
            }
            else if //...
        }
    }
}

我们响应了PreSendRequestHeaders事件,它将会在服务器端发送Header信息之前被触发。此时,如果Status Code为302(表示Response将要使客户端跳转到另一个页面去),则会清除所有即将发送的内容,并重新指定传输的信息。在这里最重要的修改就是Response Body的内容。因为客户端将要解析收到的字符串,因此我们必须发送格式为“length|type|id|content”。请注意上方红色的代码,它将会发送一段格式合法的字符串,例如“16|pageRedirect||/AnotherPage.aspx|”。

在客户端,我们可以找到下面的实现,它的作用是在收到页面重定向的信息之后跳转页面。请注意下方红色的代码:

function Sys$WebForms$PageRequestManager$_onFormSubmitCompleted(sender, eventArgs)
{
    // ...

    for (var i = 0; i < delta.length; i++) {
        var deltaNode = delta[i];
        switch (deltaNode.type) {
            case "updatePanel":
                Array.add(updatePanelNodes, deltaNode);
                break;

	    // ...

            case "pageRedirect":
                window.location.href = deltaNode.content;
                return;
            //...
        }
    }

    // ...
}

明白了这点之后,我们也就能够轻松地编写一个这样的模块了:

public class AjaxFileUploadModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PreSendRequestHeaders += new EventHandler(PreSendRequestHeadersHandler);
    }

    private void PreSendRequestHeadersHandler(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpResponse response = application.Response;

        if (response.StatusCode == 302 && 
            AjaxFileUploadUtility.IsInIFrameAsyncPostBack(application.Request.Params))
        {
            string redirectLocation = response.RedirectLocation;
            List<HttpCookie> cookies = new List<HttpCookie>(response.Cookies.Count);

            for (int i = 0; i < response.Cookies.Count; i++)
            {
                cookies.Add(response.Cookies[i]);
            }

            response.ClearContent();
            response.ClearHeaders();

            for (int i = 0; i < cookies.Count; i++)
            {
                response.AppendCookie(cookies[i]);
            }

            response.Cache.SetCacheability(HttpCacheability.NoCache);
            response.ContentType = "text/plain";

            AjaxFileUploadUtility.WriteScriptBlock(response, true);
            
            StringBuilder sb = new StringBuilder();
            TextWriter writer = new StringWriter(sb);
            AjaxFileUploadUtility.EncodeString(writer, "pageRedirect", 
                String.Empty, redirectLocation);
            response.Write(sb.Replace("*/", "*//*").ToString());

            AjaxFileUploadUtility.WriteScriptBlock(response, false);

            response.End();
        }
    }

    public void Dispose() {}
}

上方红色的代码为我们的Module与ASP.NET AJAX中的ScriptModule之间唯一的区别。我们在web.config文件中注册了AjaxFileUploadModule之后,我们在服务器端调用Redirect方法之后,在客户端就能进行跳转了。此时客户端接收到的文本如下:

<script type='text/javascript' language='javascript'>window.__f__=function()
    {/*16|pageRedirect||/AnotherPage.aspx|*/}</script>

现在,我们终于完成了所有的组件。您也不妨将其下载之后尝试一下吧,如果遇到了问题,请及时和我联系。:)

 

点击这里下载整个项目

English Version

Creative Commons License

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

Add your comment

55 条回复

  1. WOW玩家
    *.*.*.*
    链接

    WOW玩家 2007-04-12 17:03:00

    世界技术精英!

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

    老夫子系 2007-04-12 19:29:00

    强!

  3. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2007-04-12 20:56:00

    不错不错,很完整的解决方案,好像原本的UpdatePanel那样一次把所有问题都解决掉了。

  4. 无常
    *.*.*.*
    链接

    无常 2007-04-12 20:59:00

    敬礼!

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

    llinzzi[未注册用户] 2007-04-19 19:43:00

    为什么你能能这么彻底的解决问题。佩服
    我是ScriptManager.RegisterStartupScript直接输出了window.location.href ....

  6. 老赵
    admin
    链接

    老赵 2007-04-19 19:55:00

    @llinzzi
    最近即将升级,修补两个bug。:)

  7. 赤色火焰
    *.*.*.*
    链接

    赤色火焰 2007-04-24 10:56:00

    无敌牛人啊。。。修补什么BUG呢?能透露一下吗?

  8. 老赵
    admin
    链接

    老赵 2007-04-24 14:15:00

    @赤色火焰
    1、如果UpdatePanel里出现<script>元素就会出错。
    2、一个非常特别的情况下会出现的问题,几句话描述不清……

  9. 赤色火焰
    *.*.*.*
    链接

    赤色火焰 2007-04-24 22:01:00

    今天我用了,确实有这个问题。期待早日解决哦。

  10. 老赵
    admin
    链接

    老赵 2007-04-24 23:59:00

    @赤色火焰
    其实都解决了都一个多星期了,只是还没有写文章,如果需要的话来这里下载吧(v1.1的链接)。:)
    http://www.jeffzon.net/blog/page/Released-Components.aspx#ajaxfileuploadhelper

  11. 老赵
    admin
    链接

    老赵 2007-04-25 00:00:00

    @赤色火焰
    不过为什么要在UpdatePanel中放置script元素呢?

  12. 赤色火焰
    *.*.*.*
    链接

    赤色火焰 2007-04-25 19:09:00

    我是故意试的。。。

    还有,由于AJAX采用异步无刷新与数据交互。那么如果整个网站是采用SESSION来获得登录的用户。那么如果SESSION超时的话,再进行网站的操作。如果是在普通进行回发的时候,那么可以验证后重新跳转到登录页,但是AJAX是不会回发的,就直接报错了。这个是不是就是个不能解决的问题?总不能在每次操作的时候都去判断SESSION是否存在吧?希望能得到老大的解答。。我也不知道该发在哪里,就直接写这里了。希望不要见怪。

  13. 老赵
    admin
    链接

    老赵 2007-04-25 22:39:00

    @赤色火焰
    什么叫做AJAX不会回发?其实如果是正常的过期,就会出现一个302跳转,然后ASP.NET AJAX能够处理这种情况的。

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

    Aaron[未注册用户] 2007-05-16 16:21:00

    经过测试若是文档大小超过4M,上传时就会出现Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 500
    必须得在web.config里面加上 maxRequestLength 设定才行

  15. 老赵
    admin
    链接

    老赵 2007-05-16 19:51:00

    @Aaron
    这就不是这个上传组件该关心的事情了,呵呵。

  16. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-18 19:03:00

    如果动态隐藏FILEUPLOAD控件,好象就不能上传了:(

  17. 老赵
    admin
    链接

    老赵 2007-05-18 22:25:00

    @好象有个问题
    动态隐藏FileUpload控件是什么意思啊?

  18. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-19 08:32:00

    代码说话吧:)
    <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <jeffz:ajaxfileuploadhelper id="AjaxFileUploadHelper1" runat="server"></jeffz:ajaxfileuploadhelper>

    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
    &nbsp;<asp:FileUpload ID="FileUpload1" runat="server" Visible="False" />
    <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
    <asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Button" />
    <br />
    </ContentTemplate></asp:UpdatePanel>
    ******************************************
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void Button1_Click(object sender, EventArgs e)
    {
    FileUpload1.Visible = !FileUpload1.Visible;
    }
    protected void Button2_Click(object sender, EventArgs e)
    {
    FileUpload1.SaveAs("d:\\upload\\" + FileUpload1.FileName);
    }
    *********************************
    这里<asp:FileUpload ID="FileUpload1" runat="server" Visible="False" />时1.1版有问题。另,1.0版中返回SCRIPT错误云云...

  19. 老赵
    admin
    链接

    老赵 2007-05-19 21:41:00

    您把FileUpload的Visible设为了False之后页面上已经没有上传框了,自然无法上传了……

  20. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-21 07:11:00

    Button1_Click设为可见了啊,先点BUTTON1,再点BUTTON2,呵呵

  21. 老赵
    admin
    链接

    老赵 2007-05-21 13:22:00

    @好象有个问题
    有没有具体的例子给我看一下呢?通过Email发给我吧。越简单越好。

  22. Aaron[未注册用户]
    *.*.*.*
    链接

    Aaron[未注册用户] 2007-05-21 15:44:00

    延续上次档案超过大小的问题,请问该在哪里拦截这个错误

  23. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-21 17:59:00

    上面aspx,aspx.cs的主要内容都有了啊?
    好吧,不造同一个轮子,发给你了,查收

  24. 老赵
    admin
    链接

    老赵 2007-05-22 02:59:00

    @Aaron
    很难拦截,一般就不要管它,给用户看出错页面就可以了,呵呵。

  25. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-22 09:53:00

    邮件收到了么?现在我用Enable代替Visible来控制,可以用:)

  26. 老赵
    admin
    链接

    老赵 2007-05-23 02:01:00

    @好象有个问题
    收到了,不过还没有看。其实Visible和Enable的概念需要分清,一个是Render的,一个是不Render的。

  27. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-23 10:53:00

    Render?这个我不知道,惭愧,查查资料先:)
    刚学ASP.NET两个月,学AJAX.NET的时候用到您做的这个控件,感觉不错.原理还没看,呵呵,以后多多向您学习.

  28. 老赵
    admin
    链接

    老赵 2007-05-24 10:28:00

    @好象有个问题
    这是ASP.NET AJAX不是AJAX.NET,这一定要分清,完全两个不同的东西。

  29. 好象有个问题[未注册用户]
    *.*.*.*
    链接

    好象有个问题[未注册用户] 2007-05-25 10:05:00

    OK,ASP.NET AJAX,谢谢,做人要严谨:)
    问题看出来了么?

  30. 老赵
    admin
    链接

    老赵 2007-05-25 12:54:00

    @好象有个问题
    最近我事情很多,我现在积累着,稍后一块儿处理。

  31. tzzyw[未注册用户]
    *.*.*.*
    链接

    tzzyw[未注册用户] 2007-05-26 09:50:00

    能不能实现把freetextbox放到UpdatePanel中支持无刷新呢?
    现在做一个项目有这样的需求无论如何都无法实现。

  32. 老赵
    admin
    链接

    老赵 2007-05-28 15:24:00

    @tzzyw
    这个比较困难需要自己分析,如果可以的话,您不要将FreeTextBox放在UpdatePanel中吧。

  33. renhui[未注册用户]
    *.*.*.*
    链接

    renhui[未注册用户] 2007-06-04 08:28:00

    感谢老赵,真是及时雨呀,正发愁这个问题如何解决呢!支持你!

  34. Herry.Wan[未注册用户]
    *.*.*.*
    链接

    Herry.Wan[未注册用户] 2007-06-12 17:37:00

    我的错误页面不能捕获由附件过大引起的错误,我该怎么办?

  35. 老赵
    admin
    链接

    老赵 2007-06-12 18:21:00

    @Herry.Wan
    这是AJAX的功能,自然无法自动跳转到错误页面,您可以在客户端捕获之后转向那个页面。

  36. Bubble[未注册用户]
    *.*.*.*
    链接

    Bubble[未注册用户] 2007-06-18 10:33:00

    是否可以在这个基础上,把进度条也无刷新的做出来。那就更加Cool了。

  37. 老赵
    admin
    链接

    老赵 2007-06-18 11:27:00

    @Bubble
    其实这并不是一个范畴内的东西啊.

  38. Blackbear[未注册用户]
    *.*.*.*
    链接

    Blackbear[未注册用户] 2007-07-03 01:50:00

    請問点击Upload按钮之後,下面會出現進度條嗎?

  39. 老赵
    admin
    链接

    老赵 2007-07-03 01:51:00

    @Blackbear
    会的

  40. jj[未注册用户]
    *.*.*.*
    链接

    jj[未注册用户] 2007-07-12 13:32:00

    顶!做得相当不错!

  41. Carl She[未注册用户]
    *.*.*.*
    链接

    Carl She[未注册用户] 2007-07-17 23:28:00

    请教: ModalDialog中使用UpdatePanel, 并用Response.Redirect在同一对话框中转移到其它页面的方法.

    衷心感谢!

  42. 吴碧宇
    *.*.*.*
    链接

    吴碧宇 2007-07-20 01:08:00

    非常不错..这是最好的解决方案..

  43. 吴碧宇
    *.*.*.*
    链接

    吴碧宇 2007-07-20 01:08:00

    衷心感谢.........你真的很强...

  44. 南南小宝[未注册用户]
    *.*.*.*
    链接

    南南小宝[未注册用户] 2007-08-09 23:57:00

    牛人,本人很佩服牛人.刚做.net以后有什么不懂的,一定来你的网页找,呵呵

  45. 老赵
    admin
    链接

    老赵 2007-08-10 00:20:00

    @南南小宝
    欢迎来提问。:P

  46. reddatura[未注册用户]
    *.*.*.*
    链接

    reddatura[未注册用户] 2007-09-21 09:11:00

    不知道有新版本了吗
    我这里出现了500 错误

    刚开始使用还是没有的,
    现在不知道为什么出现了,
    能指点以下么

  47. reddatura[未注册用户]
    *.*.*.*
    链接

    reddatura[未注册用户] 2007-09-21 17:51:00

    老赵
    你怎么不理我呢
    msn也没有反映
    我快急死了啊

    现在发现的情况是小的文件没有问题,大的就不行了

  48. AjaxStudent[未注册用户]
    *.*.*.*
    链接

    AjaxStudent[未注册用户] 2007-11-22 11:29:00

    我的也出现了500的错误:原文
    Sys.WebForms.PageRequestManagerServerErrorException:An unknown Error has ocuured while the processing the Quest on the server .the status code retuened from the server was 500

    迫切希望赵大给与回复。

  49. 老赵
    admin
    链接

    老赵 2007-11-22 12:41:00

    这个组件问题很多,不要使用,呵呵

  50. cage[未注册用户]
    *.*.*.*
    链接

    cage[未注册用户] 2007-11-22 17:13:00

    想請一問下!!
    組件使用上都是正常
    不過我把fileupload放在DetailsView EditItemTemplate裡面
    DetailsVeiw 在Insert,Update, Cancel過程中
    為什麼有時候 狀態列顯示"完成"
    進度圖示卻還在跑!!!

  51. 蜗牛身上的一只蚂蚁
    *.*.*.*
    链接

    蜗牛身上的一只蚂蚁 2008-07-07 13:58:00

    --引用-------------------------------------------------- AjaxStudent: 我的也出现了500的错误:原文 Sys.WebForms.PageRequestManagerServerErrorException:An unknown Error has ocuured while the processing the Quest on the server .the status code retuened from the server was 500 迫切希望赵大给与回复。 -------------------------------------------------------引用-------------------------------------------------- AjaxStudent: 我的也出现了500的错误:原文 Sys.WebForms.PageRequestManagerServerErrorException:An unknown Error has ocuured while the processing the Quest on the server .the status code retuened from the server was 500 迫切希望赵大给与回复。 --------------------------------------------------------

    我的问题也一样,无法搞定。。。郁门。。。

  52. 蜗牛身上的一只蚂蚁
    *.*.*.*
    链接

    蜗牛身上的一只蚂蚁 2008-07-07 13:59:00

    我的是在母版页中出现这错误的。。。。

  53. ivw[未注册用户]
    *.*.*.*
    链接

    ivw[未注册用户] 2009-06-19 11:04:00

    赵兄你好,我试了下你写的组件,在简单的页面是没问题,但如果页面的内容比较多和复杂时就会提示 "注释未结束" 的脚本错误,导致不能正常使用.

  54. FireTiger
    218.66.36.*
    链接

    FireTiger 2010-06-21 16:58:15

    @赤色火焰 其实都解决了都一个多星期了,只是还没有写文章,如果需要的话来这里下载吧(v1.1的链接)。:) http://www.jeffzon.net/blog/page/Released-Components.aspx#ajaxfileuploadhelper

    这个下载地址过期了? 能不能发一份最新的到我邮箱:FireTiger@MFKSoft.com 谢谢

  55. FireTiger
    218.66.36.*
    链接

    FireTiger 2010-06-21 17:38:40

    "Sys.WebForms.PageRequestManagerParserErrorException: 无法分析从服务器收到的消息。之所以出现此错误,常见的原因是: 在通过调用 Response.Write() 修改响应时,将启用响应筛选器、HttpModule 或服务器跟踪。 详细信息: 分析附近的“1”时出错。"

    我的出现这种情况。。 在母版页的时候。。 没有母版正常。。

    就是 ToolkitScriptManager 和 AjaxFileUploadHelper UpdatePanel 都在母版页里。

    页面里没有Response.Write() 这个东东。。

    急啊。盼 老赵 帮忙解决下。。-

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我