Hello World
Spiga

更新:让UpdatePanel支持上传文件

2008-05-04 08:57 by 老赵, 33160 visits

在一年多前我发布了一系列文章(让UpdatePanel支持文件上传(1)(2)(3)(4)(5)),设法让UpdatePanel支持上传文件。可惜缺陷无数,当时收到了无数bug report但是我都没有时间(和兴趣)去解决,因此只是一个实验品中的实验品。这个组件还有一个问题就在于非常复杂,复杂的原因是为了解决iframe传输数据的问题。当时想了很多办法最终让IE6、7,FireFox 1.5支持了这个组件,但是对于Safari,Opera等浏览器就无能为力了。最近发现jQuery的Form插件能够在一定程度上支持我需要的功能,于是就有了简化并改进该控件的想法。虽然后来发现jQuery在这方面的表现并不如我想象中那么好……

 

客户端通信替换机制

UpdatePanel从一开始就无法支持AJAX的文件上传方式。Eilon Lipton写了一篇文章解释了这个问题的原因。文章中提供了两个绕开此问题的方法:

  1. 将“上传”按钮设为一个传统的PostBack控件而不是异步PostBack。您可以使用多种方法来这么做:例如将一个按钮放置在UpdatePanel外,将按钮设为某个UpdatePanel的PostBackTrigger,或者调用ScriptManager.RegisterPostBackControl来注册它。
  2. 建立一个不使用ASP.NET AJAX的上传页面,很多站点已经这么做了。

不过,我们为什么不使UpdatePanel兼容FileUpload控件(<input type="file" />)呢?如果可以这样,一定能够受需要使用UpdatePanel上传文件的用户欢迎。

我们首先要解决的问题是,找到一种能够将信息发送到服务器端的方法。我们都知道XMLHttpRequest只能发送字符串。在这里,我们使用和其他的异步上传文件的解决方案一样,使用iframe来上传文件。iframe元素是一个非常有用的东西,即使在AJAX这个概念出现之前,它已经被用于制作一些异步更新的效果了。

其次,我们该如何改变UpdatePanel传输数据的行为?幸亏Microsoft AJAX Library有个灵活的异步通讯层,我们可以方便创建一个UpdatePanelIFrameExecutor来继承Sys.Net.WebRequestExecutor,并且将它交给一个上传文件的WebRequest对象。因此,下面的代码可以作为我们开发组件的第一步:

Type.registerNamespace("AspNetAjaxExtensions"); 
 
AspNetAjaxExtensions.UpdatePanelIFrameExecutor = function(sourceElement)
{
    AspNetAjaxExtensions.UpdatePanelIFrameExecutor.initializeBase(this);
 
    // ...
}
 
AspNetAjaxExtensions.UpdatePanelIFrameExecutor.prototype =
{
    // ...
}
AspNetAjaxExtensions.UpdatePanelIFrameExecutor.registerClass(
    "AspNetAjaxExtensions.UpdatePanelIFrameExecutor",
     Sys.Net.WebRequestExecutor);
 
AspNetAjaxExtensions.UpdatePanelIFrameExecutor._beginRequestHandler = function(sender, e)
{
    var inputList = document.getElementsByTagName("input");
    for (var i = 0; i < inputList.length; i++)
    {
        var type = inputList[i].type;
        if (type && type.toUpperCase() == "FILE")
        {
            e.get_request().set_executor(
                new AspNetAjaxExtensions.UpdatePanelIFrameExecutor(e.get_postBackElement()));

            return;
        }
    }
}
 
Sys.Application.add_init(function()
{
    Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(
        AspNetAjaxExtensions.UpdatePanelIFrameExecutor._beginRequestHandler);
});

在上面的代码中,我们在页面初始化时监听了PageRequestManager对象的beginRequest事件。当PageRequestManager触发了一个异步请求时,我们会检查页面上是否有<input type="file" />控件。如果存在的话,则创建一个UpdatePanelIFrameExecutor实例,并分配给即将执行的WebRequest对象。

根据异步通讯层的实现,WebRequest的作用只是一个保存请求信息的容器,至于如何向服务器端发送信息则完全是Executor的事情了。事实上Executor完全可以不理会WebRequest携带的信息自行处理,而我们的UpdatePanelIFrameExecutor就是这样的玩意儿。它会改变页面上的内容,将信息Post到额外的IFrame中,并且处理从服务器端获得的数据。

 

服务器端组件

再来关注服务器端的组件。目前的主要问题是,我们如何让页面(事实上是ScriptManager控件)认为它接收到的是一个异步的回送?ScriptManager控件会在HTTP请求的Header中查找特定的项,但是我们在向IFrame中POST数据时无法修改Header。所以我们必须使用一个方法来“欺骗”ScriptManager。

目前使用的解决方案是,我们在POST数据之前在页面中隐藏的输入元素(<input type="hidden" />)中放入一个特定的标记,然后我们开发的服务器端组件(我把它叫做UpdatePanelFileUplaod)会在它的Init阶段(OnInit方法)中在Request Body中检查这个标记,然后使用反射来告诉ScriptManager目前的请求为一个异步请求。

但是事情并不像我们想象的那么简单,让我们在写代码之前来看一个方法:

internal sealed class PageRequestManager
{
    // ...
 
    internal void OnInit()
    {
        if (_owner.EnablePartialRendering && !_owner._supportsPartialRenderingSetByUser)
        {
            IHttpBrowserCapabilities browser = _owner.IPage.Request.Browser;
            bool supportsPartialRendering =
                (browser.W3CDomVersion >= MinimumW3CDomVersion) &&
                (browser.EcmaScriptVersion >= MinimumEcmaScriptVersion) &&
                browser.SupportsCallback;
 
            if (supportsPartialRendering)
            {
                supportsPartialRendering = !EnableLegacyRendering;
            }
            _owner.SupportsPartialRendering = supportsPartialRendering;
        }
 
        if (_owner.IsInAsyncPostBack)
        {
            _owner.IPage.Error += OnPageError;
        }

    }

    ...
}

上面这段代码会在ScriptManager的OnInit方法中被调用。请注意最后加粗部分的代码,“_owner”变量是当前页面上的ScriptManager。在页面收到一个真正的异步回送之后,PageRequestManager会响应页面的Error事件,并且将错误信息用它定义的格式输出。如果我们只是修改了ScriptManager的私有field,那么如果在异步回送时出现了一个未捕获的异常,那么页面就会输出客户端未知的内容,导致在客户端解析失败。所以我们必须保证这种情况下的输出和真正的异步回送是相同的,于是我们就可以使用以下的做法来解决错误处理的问题。

internal static class FileUploadUtility
{
    public static bool IsInUploadAsyncPostBack(HttpContext context)
    {
        string[] values = context.Request.Params.GetValues("__UpdatePanelUploading__");
 
        if (values == null) return false;
 
        foreach (string value in values)
        {
            if (value == "true")
            {
                return true;
            }
        }
 
        return false;
    }
}
 
 
[PersistChildren(false)]
[ParseChildren(true)]
[NonVisualControl]
public class UpdatePanelFileUpload : Control
{
    // ScriptManager members;
    private readonly static FieldInfo s_isInAsyncPostBackFieldInfo;
    private readonly static PropertyInfo s_pageRequestManagerPropertyInfo;
 
    // PageRequestManager members;
    private readonly static MethodInfo s_onPageErrorMethodInfo;
 
    static UpdatePanelFileUpload()
    {
        // Omitted: Initializing of the static members for reflection;
        ...
    }
 
    private bool m_pageInitialized = false;
 
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
 
        // Omitted: Initializing UpdatePanelFileUpload control on the page;
        ...

        this.IsInUploadAsyncPostBack = FileUploadUtility.IsInUploadAsyncPostBack(this.Context);
        if (this.IsInUploadAsyncPostBack)
        {
            s_isInAsyncPostBackFieldInfo.SetValue(ScriptManager.GetCurrent(this.Page), true);
            this.Page.Error += (sender, ea) =>
            {
                s_onPageErrorMethodInfo.Invoke(
                    this.PageRequestManager, new object[] { sender, ea });

            };
        }

    }
 
    public bool IsInUploadAsyncPostBack { get; private set; }
 
    private object m_pageRequestManager;
    private object PageRequestManager
    {
        get
        {
            if (this.m_pageRequestManager == null)
            {
                this.m_pageRequestManager = s_pageRequestManagerPropertyInfo.GetValue(
                    ScriptManager.GetCurrent(this.Page), null);
            }
 
            return this.m_pageRequestManager;
        }
    }
 
    ...
}

这段实现并不复杂。如果Request Body中的“__UpdatePanelUploading__”的值为“true”,我们就会使用反射修改ScirptManager控件中的私有变量“_isInAsyncPostBack”。此后,我们使用了自己定义的匿名方法来监听页面的Error事件,当页面的Error事件被触发时,我们定义的新方法就会将能够正确解析的内容发送给客户端。

自然,UpdatePanelFileUpload也需要将程序集中内嵌的脚本文件注册到页面中。我为组件添加了一个开关,可以让用户开发人员使用编程的方式来打开/关闭对于AJAX文件上传的支持。这部分实现更为简单:

public bool Enabled
{
    get { ... }
    set { ... }
}
 
public string ExecuteMethod
{
    get { ... }
    set { ... }
}
 
protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);
 
    ScriptManager sm = ScriptManager.GetCurrent(this.Page);
    if (sm.IsInAsyncPostBack || !sm.EnablePartialRendering ||
        this.IsInUploadAsyncPostBack || !this.Enabled)
    {
        return;
    }
 
    if (String.IsNullOrEmpty(this.ExecuteMethod))
    {
        throw new ArgumentException("Please provide the ExecuteMethod.");
    }
 
    ScriptReference script = new ScriptReference(
        "AspNetAjaxExtensions.UpdatePanelFileUpload.js",
        this.GetType().Assembly.FullName);
    ScriptManager.GetCurrent(this.Page).Scripts.Add(script);
 
    if (!String.IsNullOrEmpty(this.ExecuteMethod))
    {
        this.Page.ClientScript.RegisterStartupScript(
            this.GetType(),
            "ExecuteMethod",
            "AspNetAjaxExtensions.UpdatePanelIFrameExecutor._executeForm = " + this.ExecuteMethod + ";",
            true);
    }
}

从上面的代码中还可以看到一个ExecuteMethod属性,而这个属性最终会被拼接为一段JavaScript并注册到页面中去。这是新版UpdatePanelFileUpload控件的新特点。这个控件最关键的特性是使用iframe来传递和接受数据,而我将实现这个功能完全交由用户来实现。原因如下:

  • 使用iframe进行通信非常复杂也很难写出真正完美的代码,因此将这部分功能转移到控件外部,这样用户就可以自行修改了。
  • 一些AJAX组件提供了使用iframe进行通信的功能(例如jQuery的Form插件),但是控件无法知道用户的应用中是否已经用了其他客户端框架,因此UpdatePanelFileUpload不会与任何特定的客户端框架进行绑定。

当然,为了方便大家使用,也为了提供一个完整的解决方案,我会提供一个基于jQuery的Form插件的ExecuteMethod。在使用中也可以将其替换为适合您项目的做法,例如swfupload

 

客户端组件

UpdatePanelIFrameExecutor继承了WebRequestExecutor,因此需要实现许多方法和属性。但是我们事实上不用完整地实现所有的成员,因为客户端的异步刷信机制只会访问其中的一部分。以下是异步刷信过程中会使用的成员列表,我们必须正确地实现它们:

  • get_started: 表示一个Executor是否已经开始 了。
  • get_responseAvailable: 表示一个请求是否成功。
  • get_timedOut: 表示一个请求是否超时。
  • get_aborted: 表示一个请求是否被取消了。
  • get_responseData: 获得文本形式的Response Body。 
  • get_statusCode: 获得Response的状态代码
  • executeRequest: 执行一个请求。
  • abort: 停止正在运行的请求。

UploadPanelIFrameExecutor非常简单,只是定义了一些私有变量:

AspNetAjaxExtensions.UpdatePanelIFrameExecutor = function(sourceElement)
{
    AspNetAjaxExtensions.UpdatePanelIFrameExecutor.initializeBase(this);
 
    // for properties
    this._started = false;
    this._responseAvailable = false;
    this._timedOut = false;
    this._aborted = false;
    this._responseData = null;
    this._statusCode = null;
   
    // the element initiated the async postback
    this._sourceElement = sourceElement;
    // the form in the page.
    this._form = Sys.WebForms.PageRequestManager.getInstance()._form;
}

对于大部分属性来说,它们的实现只不过是对上面这些私有变量进行读取或写入而已,在此不提。而一个Executor最重要的莫过于它的executeRequest方法,一些回调函数,还有过期定时器之类的逻辑:

executeRequest : function()
{
    this._addAdditionalHiddenElements();
 
    var onSuccess = Function.createDelegate(this, this._onSuccess);
    var onFailure = Function.createDelegate(this, this._onFailure);
 
    this._started = true;
 
    var timeout = this._webRequest.get_timeout();
    if (timeout > 0)
    {
        this._timer = window.setTimeout(
            Function.createDelegate(this, this._onTimeout), timeout);

    }
   
    AspNetAjaxExtensions.UpdatePanelIFrameExecutor._executeForm(
        this._form, onSuccess, onFailure);

},
 
_addAdditionalHiddenElements : function() { ... },
 
_removeAdditionalHiddenElements : function() { ... },
 
_onSuccess : function(responseData)
{
    this._clearTimer();
    if (this._aborted || this._timedOut) return;
   
    this._statusCode = 200;
    this._responseAvailable = true;
    this._responseData = responseData;
   
    this._removeAdditionalHiddenElements();
    this.get_webRequest().completed(Sys.EventArgs.Empty);
},
 
_onFailure : function()
{
    this._clearTimer();
    if (this._aborted || this._timedOut) return;
   
    this._statusCode = 500;
    this._responseAvailable = false;
   
    this._removeAdditionalHiddenElements();
    this.get_webRequest().completed(Sys.EventArgs.Empty);
},
 
abort : function()
{
    this._aborted = true;
    this._clearTimer();
   
    this._removeAdditionalHiddenElements();
},
 
_onTimeout : function()
{
    this._timedOut = true;

    this._statusCode = 500;
    this._responseAvailable = false;

    this._removeAdditionalHiddenElements();
    this.get_webRequest().completed(Sys.EventArgs.Empty);

},
 
_clearTimer : function()
{
    if (this._timer != null)
    {
        window.clearTimeout(this._timer);
        delete this._timer;
    }
}

如果您了解Executor的功能,那么应该很容易看懂上面的代码:executeRequest方法用于发出请求,在executeRequest方法中还会打开一个监听是否超时的定时器,当得到回复或超时后就会调用WebRequest的completed方法(在_onSuccess和_onFailure方法内)进行通知。不过上面这段代码中还有两个特别的方法“_addAddtionalHiddenElements”和“removeAdditionalHiddenElements,从名称上就能看出,这是为这次“异步提交”而准备的额外元素。

那么我们该创建哪些附加的隐藏输入元素呢?自然我们表示“异步回送”的自定义标记是其中之一,那么剩下的还需要哪些呢?似乎我们只能通过阅读PageRequestManager的代码来找到问题的答案。还好,似乎阅读下面的代码并不困难:

function Sys$WebForms$PageRequestManager$_onFormSubmit(evt)
{
    // ...
   
    // Construct the form body
    var formBody = new Sys.StringBuilder();
    formBody.append(this._scriptManagerID + '=' + this._postBackSettings.panelID + '&');
 
    var count = form.elements.length;
    for (var i = 0; i < count; i++)
    {
        // ...
        // Traverse the input elements to construct the form body
        // ...
    }
 
    if (this._additionalInput)
    {
        formBody.append(this._additionalInput);
        this._additionalInput = null;
    }

 
    var request = new Sys.Net.WebRequest();
    // ...
    // prepare the web request object
    // ...
 
    var handler = this._get_eventHandlerList().getHandler("initializeRequest");
    if (handler) {
        var eventArgs = new Sys.WebForms.InitializeRequestEventArgs(
            request, this._postBackSettings.sourceElement);
        handler(this, eventArgs);
        continueSubmit = !eventArgs.get_cancel();
    }
 
    // ...
 
    this._request = request;
    request.invoke();
 
    if (evt) {
        evt.preventDefault();
    }
}

请注意加粗部分的代码。可以发现有两种数据需要被添加为隐藏的输入元素。其一是ScriptManager相关的信息(第一部分的红色代码),其二则是变量“_additionalInput”的内容。我们很容易得到前者的值,但是后者的内容究竟是什么呢?我们继续阅读代码:

function Sys$WebForms$PageRequestManager$_onFormElementClick(evt)
{
    var element = evt.target;
    if (element.disabled) {
        return;
    }
 
    // Check if the element that was clicked on should cause an async postback
    this._postBackSettings = this._getPostBackSettings(element, element.name);
 
    if (element.name)
    {
        if (element.tagName === 'INPUT')
        {
            var type = element.type;
            if (type === 'submit')
            {
                this._additionalInput =
                    element.name + '=' + encodeURIComponent(element.value);
            }
            else if (type === 'image')
            {
                var x = evt.offsetX;
                var y = evt.offsetY;
                this._additionalInput =
                    element.name + '.x=' + x + '&' + element.name + '.y=' + y;
            }
        }
        else if ((element.tagName === 'BUTTON') &&
            (element.name.length !== 0) && (element.type === 'submit'))
        {
            this._additionalInput = element.name + '=' + encodeURIComponent(element.value);
        }
    }
}

_onFormElmentClick方法会在用户点击form中特定元素时执行。方法会提供变量“_additionalInput”的内容,然后紧接着,我们之前分析过的_onFormSubmit方法会被调用。于是只要我们对WebRequest的body属性进行分析,就能够轻松地得知需要像form中额外添加哪些隐藏输入元素:

_addHiddenElement : function(name, value)
{
    var hidden = document.createElement("input");
    hidden.name = name;
    hidden.value = value;
    hidden.type = "hidden";
    this._form.appendChild(hidden);
    Array.add(this._hiddens, hidden);
},
 
_addAdditionalHiddenElements : function()
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
   
    this._hiddens = [];
   
    this._addHiddenElement(prm._scriptManagerID, prm._postBackSettings.panelID);
    this._addHiddenElement("__UpdatePanelUploading__", "true");
   
    var additionalInput = null;
    var element = this._sourceElement;
   
    if (element.name)
    {
        var requestBody = this.get_webRequest().get_body();
        var index = -1;
       
        if (element.tagName === 'INPUT')
        {
            var type = element.type;
            if (type === 'submit')
            {
                index = requestBody.lastIndexOf("&" + element.name + "=");
            }
            else if (type === 'image')
            {
                index = requestBody.lastIndexOf("&" + element.name + ".x=");
            }
        }
        else if ((element.tagName === 'BUTTON') && (element.name.length !== 0) &&
            (element.type === 'submit'))

        {
            index = requestBody.lastIndexOf("&" + element.name + "=");
        }
       
        if (index > 0)
        {
            additionalInput = requestBody.substring(index + 1);
        }
    }
   
    if (additionalInput)
    {
        var inputArray = additionalInput.split("&");
        for (var i = 0; i < inputArray.length; i++)
        {
            var nameValue = inputArray[i].split("=");
            this._addHiddenElement(nameValue[0], decodeURIComponent(nameValue[1]));
        }
    }
}, 

至于请求结束(超时或得到结果)后用于清除那些额外元素的方法也就顺理成章了:

_removeAdditionalHiddenElements : function()
{
    var hiddens = this._hiddens;
    delete this._hiddens;
   
    for (var i = 0; i < hiddens.length; i++)
    {
        hiddens[i].parentNode.removeChild(hiddens[i]);
    }
   
    hiddens.length = 0;
},

至此,我们的客户端组件已经编写完毕了。不过您应该产生疑问:通过IFrame传递数据的代码在哪里啊?我们接下来就来解释这个问题。

 

自定义Execute方法

  之前我已经描述过这个组件的一个特点:由于使用IFrame传递数据的逻辑非常复杂,因此我将其与控件的逻辑进行分离,这样用户就可以在需要时对这部分逻辑进行修改。此外这种做法还可以避免UpdatePanelFileUpload与某个特定的JavaScript框架绑定,用户可以选择一个符合自己应用程序的做法来实现这部分逻辑。因此UpdatePanelFileUpload释放出一个属性ExecuteMethod,它会在页面上写上“AspNetAjaxExtensions.UpdatePanelIFrameExecutor._executeForm = ...”这样的代码。而ExecuteMethod会在UpdatePanelIFrameExecutor的executeRequest方法内被调用。ExecuteMethod方法接受三个参数:“form”,“onSuccess”和“onFailure”。第一个参数为需要Post的Form,而后两个参数都为回调函数,供ExecuteMethod在合适的时候调用。

jQuery的Form插件提供了一个将内容Post到一个IFrame的功能,因此我在这里提供一个基于jQuery的方法作为示例:

function htmlDecode(s)
{
    ...
}
 
function executeForm(form, onSuccess, onFailure)
{
    $("#"+ form.id).ajaxSubmit({
        url : form.action,
        type : "POST",
        error : onFailure,
        success: getOnSuccessHandler(onSuccess)});
}
 
function getOnSuccessHandler(onSuccess)
{
    return function(content)
    {
        if (content.startsWith("<PRE>") || content.startsWith("<pre>"))
        {
            content = content.substring(5);
        }
       
        if (content.endsWith("</PRE>") || content.endsWith("</pre>"))
        {
            content = content.substring(0, content.length - 6);
        }
       
        content = htmlDecode(content);
       
        if (content.indexOf("\n") >= 0 && content.indexOf("\r\n") < 0)
        {
            content = content.replace(/\n/g, "\r\n");
        }
       
        onSuccess(content);
    }
}

原本以为jQuery的Form插件提供了一个成熟的解决方案,可惜最后发现依旧不够完美。例如会在传输的结果两边加上“<PRE>”和“</PRE>”标签,还会将其中的字符进行编码,这迫使我们在得到结果后还需要进行Html Decod——这在JavaScript中可不是一件容易实现的工作。最后我从网上找了一个JavaScript版本的HTML Decode函数才算解决这个问题。此外还有一个问题就和浏览器密切相关了:IE中的换行字符为“\r\n”,而FireFox中的换行字符为“\n”,因此同样的字符串经过IFrame的传递之后实际就改变了。在普通情况下这不会造成太大问题,不过UpdatePanel客户端的解析逻辑与字符串长度密切相关,因此我们需要将结果中的\n替换成\r\n才能让功能正常运行。同样地,我们在服务器端如果手动输出HTML时,就必须输出\r\n而不是\n。

经过我的简单测试,这个方法能够支持IE6+以及FireFox 1.5+的浏览器,不过没有测试过Safari或Opera浏览器。理论上,您可以使用更好的办法来替换这个基于jQuery的实现,甚至您可以避免使用IFrame传递的方式,而改用其他的解决方案,例如swfupload。如果您发现示例中的方法有什么问题,或者有更好的做法请联系我。

 

控件的使用

由于UpdatePanelFileUpload控件的工作原理是欺骗ScriptManager,将其修改为普通异步调用的状态,因此我们要尽可能早地做到这一点。所以在使用这个控件时必须将其紧跟着ScirptManager摆放,页面中的ScriptManager和UpdatePanelFileUpload控件之间存在任何其他ASP.NET AJAX控件,就可能会产生一些不可预知的问题。以下是附件中的使用示例:

<script type="text/C#" runat="server">
    protected void btnUpload_Click(object sender, EventArgs e)
    {
        this.lblFileSize.Text = this.fileUpload.PostedFile.ContentLength.ToString();
    }
</script>
 
<form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="sm">
        <Scripts>
            <asp:ScriptReference Path="Scripts/jquery-1.2.3.js" />
            <asp:ScriptReference Path="Scripts/jquery.form.js" />
        </Scripts>
    </asp:ScriptManager>
    <ajaxExt:UpdatePanelFileUpload ID="UpdatePanelFileUpload1" runat="server"
        ExecuteMethod="executeForm" />
 
    <asp:UpdatePanel runat="server" ID="up1">
        <ContentTemplate>
            <%= DateTime.Now %><br />
            <asp:Label runat="server" ID="lblFileSize" />
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="btnUpload" />
        </Triggers>
    </asp:UpdatePanel>
 
    <asp:FileUpload runat="server" ID="fileUpload" />
    <asp:Button runat="server" ID="btnUpload" Text="Upload"
        onclick="btnUpload_Click" />
</form>

 

附件:UpdatePanelFileUpload_20080503.zip

Creative Commons License

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

Add your comment

54 条回复

  1. 伍迷
    *.*.*.*
    链接

    伍迷 2008-05-03 16:34:00

    还没细看,先支持一下,写得不容易。

  2. mr bian[未注册用户]
    *.*.*.*
    链接

    mr bian[未注册用户] 2008-05-03 16:57:00

    刚好我网站的上传不好用,,,,试试你的,
    我的网站http://www.kksz.cn/
    希望大家能帮我增加点击

  3. 无常
    *.*.*.*
    链接

    无常 2008-05-03 16:58:00

    很佩服博主的执着~~

    可是,对于大文件上传,为什么不用FlashUpload之类的组件呢?为何非要钻UpdatePanel这种死牛角尖~~

  4. 李战
    *.*.*.*
    链接

    李战 2008-05-03 17:07:00

    先占坑,再慢慢看。

  5. 苏州婚纱[未注册用户]
    *.*.*.*
    链接

    苏州婚纱[未注册用户] 2008-05-03 17:17:00

    嗯。占楼上旁边的坑。

  6. Scott Xu(南方小鬼)
    *.*.*.*
    链接

    Scott Xu(南方小鬼) 2008-05-03 18:42:00

    在占旁边的旁边的坑

  7. 老赵
    admin
    链接

    老赵 2008-05-03 18:55:00

    --引用--------------------------------------------------
    无常: 很佩服博主的执着~~
    可是,对于大文件上传,为什么不用FlashUpload之类的组件呢?为何非要钻UpdatePanel这种死牛角尖~~
    --------------------------------------------------------
    您似乎没有看这篇文章……本来就不是个上传大文件的解决方案,本来就是为了解决FileUpload和UpdatePanel的兼容问题。上传大文件和UpdatePanel本就没有任何关系,只要FileUpload和UpdatePanel没有淘汰,这个解决方案就有意义。

  8. 怪怪
    *.*.*.*
    链接

    怪怪 2008-05-03 19:21:00

    唉...

    说实话, 我现在越来越烦这种事情了, 微软的一个设计(当然不是问题, 因为FileUpload的时候恐怕根本无法预测UpdatePanel), 就要让我们投入那么多精力。

    完全放弃asp.net的方式, 又不甘心...

  9. wjx[未注册用户]
    *.*.*.*
    链接

    wjx[未注册用户] 2008-05-03 19:45:00

    FileUpload是标准,不是ms设计的。

  10. 今日事[未注册用户]
    *.*.*.*
    链接

    今日事[未注册用户] 2008-05-03 19:48:00

    老赵 的技术很精很深,佩服...

    没用过asp.net ajax,有空研究下

  11. 老赵
    admin
    链接

    老赵 2008-05-03 20:41:00

    @怪怪
    我觉得不烦啊,因为我一直是这种方式思考的:
    一个解决方案如果没有解决所有问题,只要不会造成其他问题倒退,那么就是好的,不算失误。
    要解决真正这个问题,其实只能修改XmlHttpRequest对象,比如增加点attachFile之类的方法,呵呵,可惜就会带来非常不安全的事情。
    微软发明这个东西的时候不可能没想到这点啊——

  12. MIC[未注册用户]
    *.*.*.*
    链接

    MIC[未注册用户] 2008-05-03 20:49:00

    不错,老赵的文章都很实用

  13. Angel Lucifer
    *.*.*.*
    链接

    Angel Lucifer 2008-05-03 22:46:00

    虽然对前端技术不熟悉,还是顶一下。
    写的这么多,不容易啊。
    :-)

  14. 狼Robot
    *.*.*.*
    链接

    狼Robot 2008-05-04 01:56:00

    学习

  15. 怪怪
    *.*.*.*
    链接

    怪怪 2008-05-04 05:39:00

    @Jeffrey Zhao
    我指的烦, 指的是在某些方面使用ASP.NET框架带来的烦..., 就是说咱们解决这个问题的时候, 很多工作都是为了维持它的东西按正确的方式运转。

    比如一开始MS的控件很好使是由于WebForm的整体设计还不错, 然后某个新东西出现了, 这些控件仍然可以很好的配合新东西而我们又无需做什么工作, 这样就不烦了。 但是指望任何人一开始就把未来给预测了又是不可能的。

    我现在老觉得, 这些东西实现起来又不困难, 自己封装一下内部使用问题也不大。 如果自己搞得工作量其实并不大于克服由框架或工具带来的麻烦的工作量, 都是练练手, 我干嘛要去鼓捣别人弄得东西呢, 就是烦这个。

  16. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2008-05-04 06:54:00

    "使用反射修改ScirptManager控件中的私有变量_isInAsyncPostBack"--这个会提升整个网站的Trust Level等级,不是很爽。

    还没细看,不过,在异步提交时将file post到iframe是不是意味着其实点一下按钮做了两个提交,是否要自己考虑事务同步的问题,比如我的一次提交既有控件传递的参数又有上传的二进制文件?现有的方案有考虑吗?

    asp.net ajax有没有办法让异步提交本身就用iframe暂时代替xmlhttprequest来处理这次提交呢,如果可行,就不需要在两个request中分别处理提交的数据和binary文件了?以前的页面callback支持中,如果浏览器不支持xmlhttprequest,会用iframe处理异步提交,没有仔细研究过asp.net ajax这部分的代码,只是一个设想。

  17. 李战
    *.*.*.*
    链接

    李战 2008-05-04 09:04:00

    强烈关注本问题的讨论...

  18. wmj[未注册用户]
    *.*.*.*
    链接

    wmj[未注册用户] 2008-05-04 09:18:00

    good

  19. BAsil
    *.*.*.*
    链接

    BAsil 2008-05-04 09:21:00

    收藏先

  20. 老赵
    admin
    链接

    老赵 2008-05-04 09:23:00

    @Teddy's Knowledge Base
    “这个会提升整个网站的Trust Level等级,不是很爽。”
    这些问题只会在虚拟托管服务器遇到问题,否则我们可以把程序集strong naming一下,或者加上ReflectionPermission,不需要真的Full Trust的。
    没有两次提交,要么XmlHttpRequest,要么iframe,我现在的做法就是“让异步提交本身就用iframe暂时代替xmlhttprequest来处理这次提交”,post到iframe里的不只是file,而是整个form。

  21. 老赵
    admin
    链接

    老赵 2008-05-04 09:27:00

    @怪怪
    鼓捣别人的东西,是因为不用从头做起阿,呵呵。

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

    neo08[未注册用户] 2008-05-04 10:49:00

    既然用了iframe,能不能实现进度条呢?

  23. 老赵
    admin
    链接

    老赵 2008-05-04 11:02:00

    @neo08
    进度条与否和有没有用iframe没有任何关系,也和UpdatePanel没有关系。

  24. romce
    *.*.*.*
    链接

    romce 2008-05-04 11:11:00

    老赵就是强啊,在这里又发现好东西了

  25. neo08[未注册用户]
    *.*.*.*
    链接

    neo08[未注册用户] 2008-05-04 11:13:00

    我知道iframe和进度条没有关系,我只是想知道如何在您这个方法上实现进度条呢?

  26. 老赵
    admin
    链接

    老赵 2008-05-04 11:25:00

    @neo08
    很麻烦,如果要进度条的话,基于swfupload实现一个新的ExecuteMethod吧。

  27. foshan[未注册用户]
    *.*.*.*
    链接

    foshan[未注册用户] 2008-05-04 12:05:00

    上次用了赵大师的这个第一代作品,感觉很好用,现在又改进了,谢谢赵大师。

  28. 老赵
    admin
    链接

    老赵 2008-05-04 12:21:00

    @foshan
    很汗,上次怎么可能好用。

  29. peace
    *.*.*.*
    链接

    peace 2008-05-04 13:34:00

    无条件顶哈

  30. 天生俪姿
    *.*.*.*
    链接

    天生俪姿 2008-05-05 08:43:00

    嗯写的不错,坚持看了一半,下面的内容完全看不懂。
    不过还是支持下吧~!又能看到老赵有新东西写出来了~!
    嘿嘿~!

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

    Hand[未注册用户] 2008-05-19 17:24:00

    其实我想问一个问题。就是为什么fileupload控件在updatepanel里上传不了文件呢?就是说服务器端捕捉不了fileupload控件的数据。因为我看了老赵关于这个主题的几篇文章都看不到这个问题的答案。本人菜,所以想知道一下

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

    SNS[未注册用户] 2008-05-19 17:48:00

    老赵能不能给个完整的示例?试了好多次老是不工作……

  33. 老赵
    admin
    链接

    老赵 2008-05-19 17:50:00

    @Hand
    因为XMLHttpRequest不支持上传文件

  34. 老赵
    admin
    链接

    老赵 2008-05-19 17:50:00

    @SNS
    我给的难道不是一个完整的示例?

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

    SNS[未注册用户] 2008-05-19 18:07:00

    压缩包里面没有包含 jquery 和 htmlDecode 的脚本,我自己下载后建立的测试,老是有脚本错误,不知道哪里没有搞对

  36. 老赵
    admin
    链接

    老赵 2008-05-19 18:15:00

    @SNS
    不好意思,我重新上传了。

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

    SNS[未注册用户] 2008-05-19 18:32:00

    重新下载的还是出错
    <TABLE width=400>
    <P style="FONT: 13pt/15pt 宋体, MS Song">无法显示 XML 页。
    <P style="FONT: 9pt/14pt 宋体, MS Song">使用 样式表无法查看 XML 输入。请更正错误然后单击 <A href="javascript:location.reload()" target=_self>刷新</A>按钮,或以后重试。
    <HR>

    <P style="FONT: bold 9pt/14pt 宋体, MS Song">文档的顶层无效。处理资源 'http://test/UpdatePanelFileUpload.aspx' 时出错。第 1 行,位置: 1 </P>……

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

    aim[未注册用户] 2008-05-28 11:54:00

    下载的WebAppDEMO里没有HTMLPage1.htm
    prototype-1.6.0.2.js
    等文件
    而且一运行就出错

  39. 菌哥
    *.*.*.*
    链接

    菌哥 2008-06-07 12:16:00

    我下载了代码,出现和SNS一样的错误

  40. 菌哥
    *.*.*.*
    链接

    菌哥 2008-06-07 13:35:00

  41. 宝宝爱宝宝[未注册用户]
    *.*.*.*
    链接

    宝宝爱宝宝[未注册用户] 2008-06-16 10:56:00

    还是不行,发示例试一下再发嘛.

  42. Stat.Z[未注册用户]
    *.*.*.*
    链接

    Stat.Z[未注册用户] 2008-07-17 18:18:00

    --引用--------------------------------------------------
    SNS: 重新下载的还是出错
    &lt;TABLE width=400&gt;
    &lt;P style=&quot;FONT: 13pt/15pt 宋体, MS Song&quot;&gt;无法显示 XML 页。
    &lt;P style=&quot;FONT: 9pt/14pt 宋体, MS Song&quot;&gt;使用 样式表无法查看 XML 输入。请更正错误然后单击 &lt;A href=&quot;javascript:location.reload()&quot; target=_self&gt;刷新&lt;/A&gt;按钮,或以后重试。
    &lt;HR&gt;

    &lt;P style=&quot;FONT: bold 9pt/14pt 宋体, MS Song&quot;&gt;文档的顶层无效。处理资源 '<a href="http://test/UpdatePanelFileUpload.aspx" target="_new" rel="nofollow">http://test/UpdatePanelFileUpload.aspx</a>' 时出错。第 1 行,位置: 1 &lt;/P&gt;……
    --------------------------------------------------------
    我也查了,firefox IE7下没有部问,但在Ie6下就会出现这样的情况

  43. 阿滨
    *.*.*.*
    链接

    阿滨 2008-07-23 17:20:00

    怎么我运行在.net2.0上面会iis就死掉!能不能发个2.0版本的a

  44. Yonggang Song[未注册用户]
    *.*.*.*
    链接

    Yonggang Song[未注册用户] 2008-09-14 16:40:00

    偶才知道什么是高人!

  45. 只是过路人[未注册用户]
    *.*.*.*
    链接

    只是过路人[未注册用户] 2008-09-16 15:24:00

    在看不懂的人面前.这叫高人.但在一些真正做事的人面前...这就叫....
    如果一个无刷新上传都被你写得那么复杂那么久的话,这还叫高手?

  46. 老赵
    admin
    链接

    老赵 2008-09-16 16:42:00

    --引用--------------------------------------------------
    只是过路人: 在看不懂的人面前.这叫高人.但在一些真正做事的人面前...这就叫....
    如果一个无刷新上传都被你写得那么复杂那么久的话,这还叫高手?
    --------------------------------------------------------
    我写代码一向不喜欢复杂,所以我可以说,你一定没有仔细看过文章内容,甚至连问题本身的需求都没有搞清楚。

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

    张新[未注册用户] 2008-10-19 23:09:00

    老赵的要顶一下,我是你的粉丝

  48. Selfocus
    *.*.*.*
    链接

    Selfocus 2008-10-20 17:17:00

    我来评两句

  49. kan_xing[未注册用户]
    *.*.*.*
    链接

    kan_xing[未注册用户] 2008-11-13 18:00:00

    国外Element-IT.PowUpload 不知道对你有没有用 一定要做好这个控件
    看到外国有许多好控件(fck等)。。。。。。并专门为它整个网站 下载量非常高
    如果你能把你的这个做的很完美。。。那也很棒了。。。支持你啊
    希望我这次下载的没有bug 很久以前我用过一次你的这个控件 用上就出bug
    希望这次别在有了

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

    kan_xing[未注册用户] 2008-11-13 18:14:00

    对啊 版本不兼容啊 能发个2.0的吗?

  51. 体育皇帝
    *.*.*.*
    链接

    体育皇帝 2008-12-05 12:33:00

    我把UpdatePanelFileUpload托入网页,就报错了...创建控件失败..我用的是VS2005,为什么啊?DELL文件我已经引入了啊
    请老赵帮解决下啊

  52. 体育皇帝
    *.*.*.*
    链接

    体育皇帝 2008-12-05 12:34:00

    AjaxFileUploadHelper这个是你以前弄的一个,这个好用!
    为什么UpdatePanelFileUpload就不好用了呢?

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

    邹杰[未注册用户] 2009-02-11 18:22:00

    找个,你个的附件貌似不能用,,,,我打开的时候缺少一个东西,但是增加了bin里面的,还是提示缺少引用,我比较郁闷了,找个你做个控件出来吧..更方便一些

  54. cjdxhc
    116.226.192.*
    链接

    cjdxhc 2010-05-17 11:41:40

    赵哥,我查看了下,你这个是基于VS2008开发的.

    可以给个2.0的版本吗?因为我们现在的项目是vs 2005开发的

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我