让UpdatePanel支持文件上传(2):服务器端组件
目前使用的解决方案是,我们在POST数据之前在页面中隐藏的输入元素(<input type="hidden" />)中放入一个特定的标记,然后我们开发的服务器端组件(我把它叫做AjaxFileUplaodHelper)会在它的Init阶段(OnInit方法)中在Request Body中检查这个标记,然后使用反射来告诉ScriptManager目前的请求为一个异步请求。
但是事情并不像我们想象的那么简单,让我们在写代码之前来看一个方法:
PageRequestManager.OnInit
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 AjaxFileUploadUtility
...{
internal static bool IsInIFrameAsyncPostBack(NameValueCollection requestBody)
...{
string[] values = requestBody.GetValues("__AjaxFileUploading__");
![]()
if (values == null) return false;
![]()
foreach (string value in values)
...{
if (value == "__IsInAjaxFileUploading__")
...{
return true;
}
}
![]()
return false;
}
![]()
// ...
}
![]()
[PersistChildren(false)]
[ParseChildren(true)]
[NonVisualControl]
public class AjaxFileUploadHelper : Control
...{
// ScriptManager members;
private static FieldInfo isInAsyncPostBackFieldInfo;
private static PropertyInfo pageRequestManagerPropertyInfo;
![]()
// PageRequestManager members;
private static MethodInfo onPageErrorMethodInfo;
private static MethodInfo renderPageCallbackMethodInfo;
![]()
![]()
static AjaxFileUploadHelper()
...{
Type scriptManagerType = typeof(ScriptManager);
isInAsyncPostBackFieldInfo = scriptManagerType.GetField(
"_isInAsyncPostBack",
BindingFlags.Instance | BindingFlags.NonPublic);
pageRequestManagerPropertyInfo = scriptManagerType.GetProperty(
"PageRequestManager",
BindingFlags.Instance | BindingFlags.NonPublic);
![]()
Assembly assembly = scriptManagerType.Assembly;
Type pageRequestManagerType = assembly.GetType("System.Web.UI.PageRequestManager");
onPageErrorMethodInfo = pageRequestManagerType.GetMethod(
"OnPageError", BindingFlags.Instance | BindingFlags.NonPublic);
renderPageCallbackMethodInfo = pageRequestManagerType.GetMethod(
"RenderPageCallback", BindingFlags.Instance | BindingFlags.NonPublic);
}
![]()
public static AjaxFileUploadHelper GetCurrent(Page page)
...{
return page.Items[typeof(AjaxFileUploadHelper)] as AjaxFileUploadHelper;
}
![]()
private bool isInAjaxUploading = false;
![]()
protected override void OnInit(EventArgs e)
...{
base.OnInit(e);
![]()
if (this.Page.Items.Contains(typeof(AjaxFileUploadHelper)))
...{
throw new InvalidOperationException("One AjaxFileUploadHelper per page.");
}
![]()
this.Page.Items[typeof(AjaxFileUploadHelper)] = this;
![]()
this.EnsureIsInAjaxFileUploading();
}
![]()
private void EnsureIsInAjaxFileUploading()
...{
this.isInAjaxUploading = AjaxFileUploadUtility.IsInIFrameAsyncPostBack(this.Page.Request.Params);
![]()
if (this.isInAjaxUploading)
...{
isInAsyncPostBackFieldInfo.SetValue(
ScriptManager.GetCurrent(this.Page),
true);
![]()
this.Page.Error += new EventHandler(Page_Error);
}
}
![]()
private void Page_Error(object sender, EventArgs e)
...{
// ...
}
![]()
![]()
private object _PageRequestManager;
![]()
private object PageRequestManager
...{
get
...{
if (this._PageRequestManager == null)
...{
this._PageRequestManager = pageRequestManagerPropertyInfo.GetValue(
ScriptManager.GetCurrent(this.Page), null);
}
![]()
return this._PageRequestManager;
}
}
![]()
// ...
}
自然,AjaxFileUploadHelper也需要将程序集中内嵌的脚本文件注册到页面中。我为组件添加了一个开关,可以让用户开发人员使用编程的方式来打开/关闭对于AJAX文件上传的支持。这部分实现更为简单:
如果用户希望关闭对于AJAX文件上传的支持,他可以使用下面的代码将页面上AjaxFileUploadHelper控件的SupportAjaxUpload属性关闭:
等一下,这是什么?我是指在“OnPreRender”方法中的代码:
截获输出方式
if (this.isInAjaxUploading)
...{
this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback));
}
究竟什么是“AjaxFileUploadUtility.WriteScriptBlock”方法呢?我们为什么要这样写?其实这么做的目的是为了兼容各种浏览器,使它们都能够正确通过iframe正确收到服务器端获得的信息。这可以说是整个项目中最有技巧的部分了,我将会使用一个部分来单独讲一下这部分的机制。
