- 将“上传”按钮设为一个传统的PostBack控件而不是异步PostBack。您可以使用多种方法来这么做:例如将一个按钮放置在UpdatePanel外,将按钮设为某个UpdatePanel的PostBackTrigger,或者调用ScriptManager.RegisterPostBackControl来注册它。
- 建立一个不使用ASP.NET AJAX的上传页面,很多站点已经这么做了。
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中,并且处理从服务器端获得的数据。
目前使用的解决方案是,我们在POST数据之前在页面中隐藏的输入元素(<input type="hidden" />)中放入一个特定的标记,然后我们开发的服务器端组件(我把它叫做UpdatePanelFileUplaod)会在它的Init阶段(OnInit方法)中在Request Body中检查这个标记,然后使用反射来告诉ScriptManager目前的请求为一个异步请求。
但是事情并不像我们想象的那么简单,让我们在写代码之前来看一个方法:
{
// ...
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;
}
}
...
}
{
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文件上传的支持。这部分实现更为简单:
{
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);
}
}
- 使用iframe进行通信非常复杂也很难写出真正完美的代码,因此将这部分功能转移到控件外部,这样用户就可以自行修改了。
- 一些AJAX组件提供了使用iframe进行通信的功能(例如jQuery的Form插件),但是控件无法知道用户的应用中是否已经用了其他客户端框架,因此UpdatePanelFileUpload不会与任何特定的客户端框架进行绑定。
UpdatePanelIFrameExecutor继承了WebRequestExecutor,因此需要实现许多方法和属性。但是我们事实上不用完整地实现所有的成员,因为客户端的异步刷信机制只会访问其中的一部分。以下是异步刷信过程中会使用的成员列表,我们必须正确地实现它们:
- get_started: 表示一个Executor是否已经开始 了。
- get_responseAvailable: 表示一个请求是否成功。
- get_timedOut: 表示一个请求是否超时。
- get_aborted: 表示一个请求是否被取消了。
- get_responseData: 获得文本形式的Response Body。
- get_statusCode: 获得Response的状态代码
- executeRequest: 执行一个请求。
- abort: 停止正在运行的请求。
UploadPanelIFrameExecutor非常简单,只是定义了一些私有变量:
{
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;
}
{
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;
}
}
{
// ...
// 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();
}
}
{
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);
}
}
}
{
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]));
}
}
},
{
var hiddens = this._hiddens;
delete this._hiddens;
for (var i = 0; i < hiddens.length; i++)
{
hiddens[i].parentNode.removeChild(hiddens[i]);
}
hiddens.length = 0;
},
{
...
}
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);
}
}
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>