|
 |
栏目导栏 |
|
| |
|
|
|
|
 |
资料搜索 |
|
| |
|
|
|
|
 |
热门文章 |
|
| |
|
|
|
|
 |
最新文章 |
|
| |
|
|
|
| |
| |
|
|
|
|
UpdatePanel从一开始就无法支持AJAX的文件上传方式。Eilon Lipton写了一篇文章解释了这个问题的原因。文章中提供了两个绕开此问题的方法: Wx7Linux联盟 Wx7Linux联盟 将“上传”按钮设为一个传统的PostBack控件而不是异步PostBack。您可以使用多种方法来这么做:例如将一个按钮放置在UpdatePanel外,将按钮设为某个UpdatePanel的PostBackTrigger,或者调用ScriptManager.RegisterPostBackControl来注册它。 Wx7Linux联盟 建立一个不使用ASP.NET AJAX的上传页面,很多站点已经这么做了。 Wx7Linux联盟 不过,我们为什么不使UpdatePanel兼容FileUpload控件(<input type="file" />)呢?如果可以这样,一定能够受需要使用UpdatePanel上传文件的用户欢迎。 Wx7Linux联盟 Wx7Linux联盟 我们首先要解决的问题是,找到一种能够将信息发送到服务器端的方法。我们都知道XMLHttpRequest只能发送字符串。在这里,我们使用和其他的异步上传文件的解决方案一样,使用iframe来上传文件。iframe元素是一个非常有用的东西,即使在AJAX这个概念出现之前,它已经被用于制作一些异步更新的效果了。 Wx7Linux联盟 Wx7Linux联盟 其次,我们应该如何改变当前传输数据的行为呢?幸亏Microsoft AJAX Library有着强大的异步通讯层,我们可以方便创建一个UpdatePanelIFrameExetender来继承Sys.Net.WebRequestExecutor,并且将它交给一个上传文件的WebRequest对象。因此,下面的代码可以作为我们开发组件的第一步: Wx7Linux联盟 Wx7Linux联盟 第一步 Wx7Linux联盟 Type.registerNamespace("Jeffz.Web"); Wx7Linux联盟 Wx7Linux联盟 // the new executor will use the element witch initiated the async postback. Wx7Linux联盟 Jeffz.Web.UpdatePanelIFrameExecutor = function(sourceElement) Wx7Linux联盟 { Wx7Linux联盟 // ... Wx7Linux联盟 } Wx7Linux联盟 Jeffz.Web.UpdatePanelIFrameExecutor.prototype = Wx7Linux联盟 { Wx7Linux联盟 // ... Wx7Linux联盟 } Wx7Linux联盟 Jeffz.Web.UpdatePanelIFrameExecutor.registerClass( Wx7Linux联盟 "Jeffz.Web.UpdatePanelExecutor", Sys.Net.WebRequestExecutor); Wx7Linux联盟 Wx7Linux联盟 Jeffz.Web.UpdatePanelIFrameExecutor._beginRequestHandler = function(sender, e) Wx7Linux联盟 { Wx7Linux联盟 var inputList = document.getElementsByTagName("input"); Wx7Linux联盟 for (var i = 0; i < inputList.length; i++) Wx7Linux联盟 { Wx7Linux联盟 var type = inputList[i].type; Wx7Linux联盟 if (type && type.toUpperCase() == "FILE") Wx7Linux联盟 { Wx7Linux联盟 e.get_request().set_executor( Wx7Linux联盟 new Jeffz.Web.UpdatePanelExecutor(e.get_postBackElement())); Wx7Linux联盟 return; Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Sys.Application.add_init(function() Wx7Linux联盟 { Wx7Linux联盟 Sys.WebForms.PageRequestManager.getInstance().add_beginRequest( Wx7Linux联盟 Jeffz.Web.UpdatePanelIFrameExecutor._beginRequestHandler); Wx7Linux联盟 }); Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 在上面的代码段中,我们在页面初始化时监听了PageRequestManager对象的beginRequest事件。当PageRequestManager触发了一个异步请求时,我们会检查页面上是否有<input type="file" />控件。如果存在的话,则创建一个新的UpdatePanelIFrameExecutor实例,并分配给即将执行的WebRequest对象。 Wx7Linux联盟 Wx7Linux联盟 根据异步通讯层的实现,WebRequest的作用只是一个保存请求信息的容器,至于如何向服务器端发送信息则完全是Executor的事情了。事实上Executor完全可以不理会WebRequest携带的信息自行处理,而我们的UpdatePanelIFrameExecutor就是这样的玩意儿。它会改变页面上的内容,将信息Post到IFrame元素中,并且处理从服务器端获得的数据。我们现在来关注服务器端的组件。目前的主要问题是,我们如何让页面(事实上是ScriptManager控件)认为它接收到的是一个异步的回送?ScriptManager控件会在HTTP请求的Header中查找特定的项,但是我们在向IFrame中POST数据时无法修改Header。所以我们必须使用一个方法来“欺骗”ScriptManager。 Wx7Linux联盟 Wx7Linux联盟 目前使用的解决方案是,我们在POST数据之前在页面中隐藏的输入元素(<input type="hidden" />)中放入一个特定的标记,然后我们开发的服务器端组件(我把它叫做AjaxFileUplaodHelper)会在它的Init阶段(OnInit方法)中在Request Body中检查这个标记,然后使用反射来告诉ScriptManager目前的请求为一个异步请求。 Wx7Linux联盟 Wx7Linux联盟 但是事情并不像我们想象的那么简单,让我们在写代码之前来看一个方法: Wx7Linux联盟 Wx7Linux联盟 PageRequestManager.OnInit Wx7Linux联盟 internal sealed class PageRequestManager Wx7Linux联盟 { Wx7Linux联盟 // ... Wx7Linux联盟 Wx7Linux联盟 internal void OnInit() Wx7Linux联盟 { Wx7Linux联盟 if (_owner.EnablePartialRendering && !_owner._supportsPartialRenderingSetByUser) Wx7Linux联盟 { Wx7Linux联盟 IHttpBrowserCapabilities browser = _owner.IPage.Request.Browser; Wx7Linux联盟 bool supportsPartialRendering = Wx7Linux联盟 (browser.W3CDomVersion >= MinimumW3CDomVersion) && Wx7Linux联盟 (browser.EcmaScriptVersion >= MinimumEcmaScriptVersion) && Wx7Linux联盟 browser.SupportsCallback; Wx7Linux联盟 Wx7Linux联盟 if (supportsPartialRendering) Wx7Linux联盟 { Wx7Linux联盟 supportsPartialRendering = !EnableLegacyRendering; Wx7Linux联盟 } Wx7Linux联盟 _owner.SupportsPartialRendering = supportsPartialRendering; Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 if (_owner.IsInAsyncPostBack) Wx7Linux联盟 { Wx7Linux联盟 _owner.IPage.Error += OnPageError; Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 // ... Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 上面这段代码会在ScriptManager的OnInit方法中被调用。请注意红色部分的代码,“_owner”变量是当前页面上的ScriptManager。在页面受到一个真正的异步会送之后,PageRequestManager会响应页面的Error事件,并且将错误信息用它定义的格式输出。如果我们只是修改了ScriptManager的私有field,那么如果在异步回送时出现了一个未捕获的异常,那么页面就会输出客户端未知的内容,导致在客户端解析失败。所以我们必须保证这种情况下的输出和真正的异步回送是相同的,所以我们就可以使用以下的做法来解决错误处理的问题。 Wx7Linux联盟 Wx7Linux联盟 代码实现 Wx7Linux联盟 internal static class AjaxFileUploadUtility Wx7Linux联盟 { Wx7Linux联盟 internal static bool IsInIFrameAsyncPostBack(NameValueCollection requestBody) Wx7Linux联盟 { Wx7Linux联盟 string[] values = requestBody.GetValues("__AjaxFileUploading__"); Wx7Linux联盟 Wx7Linux联盟 if (values == null) return false; Wx7Linux联盟 Wx7Linux联盟 foreach (string value in values) Wx7Linux联盟 { Wx7Linux联盟 if (value == "__IsInAjaxFileUploading__") Wx7Linux联盟 { Wx7Linux联盟 return true; Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 return false; Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 // ... Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 [PersistChildren(false)] Wx7Linux联盟 [ParseChildren(true)] Wx7Linux联盟 [NonVisualControl] Wx7Linux联盟 public class AjaxFileUploadHelper : Control Wx7Linux联盟 { Wx7Linux联盟 // ScriptManager members; Wx7Linux联盟 private static FieldInfo isInAsyncPostBackFieldInfo; Wx7Linux联盟 private static PropertyInfo pageRequestManagerPropertyInfo; Wx7Linux联盟 Wx7Linux联盟 // PageRequestManager members; Wx7Linux联盟 private static MethodInfo onPageErrorMethodInfo; Wx7Linux联盟 private static MethodInfo renderPageCallbackMethodInfo; Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 static AjaxFileUploadHelper() Wx7Linux联盟 { Wx7Linux联盟 Type scriptManagerType = typeof(ScriptManager); Wx7Linux联盟 isInAsyncPostBackFieldInfo = scriptManagerType.GetField( Wx7Linux联盟 "_isInAsyncPostBack", Wx7Linux联盟 BindingFlags.Instance | BindingFlags.NonPublic); Wx7Linux联盟 pageRequestManagerPropertyInfo = scriptManagerType.GetProperty( Wx7Linux联盟 "PageRequestManager", Wx7Linux联盟 BindingFlags.Instance | BindingFlags.NonPublic); Wx7Linux联盟 Wx7Linux联盟 Assembly assembly = scriptManagerType.Assembly; Wx7Linux联盟 Type pageRequestManagerType = assembly.GetType("System.Web.UI.PageRequestManager"); Wx7Linux联盟 onPageErrorMethodInfo = pageRequestManagerType.GetMethod( Wx7Linux联盟 "OnPageError", BindingFlags.Instance | BindingFlags.NonPublic); Wx7Linux联盟 renderPageCallbackMethodInfo = pageRequestManagerType.GetMethod( Wx7Linux联盟 "RenderPageCallback", BindingFlags.Instance | BindingFlags.NonPublic); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 public static AjaxFileUploadHelper GetCurrent(Page page) Wx7Linux联盟 { Wx7Linux联盟 return page.Items[typeof(AjaxFileUploadHelper)] as AjaxFileUploadHelper; Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 private bool isInAjaxUploading = false; Wx7Linux联盟 Wx7Linux联盟 protected override void OnInit(EventArgs e) Wx7Linux联盟 { Wx7Linux联盟 base.OnInit(e); Wx7Linux联盟 Wx7Linux联盟 if (this.Page.Items.Contains(typeof(AjaxFileUploadHelper))) Wx7Linux联盟 { Wx7Linux联盟 throw new InvalidOperationException("One AjaxFileUploadHelper per page."); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 this.Page.Items[typeof(AjaxFileUploadHelper)] = this; Wx7Linux联盟 Wx7Linux联盟 this.EnsureIsInAjaxFileUploading(); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 private void EnsureIsInAjaxFileUploading() Wx7Linux联盟 { Wx7Linux联盟 this.isInAjaxUploading = AjaxFileUploadUtility.IsInIFrameAsyncPostBack(this.Page.Request.Params); Wx7Linux联盟 Wx7Linux联盟 if (this.isInAjaxUploading) Wx7Linux联盟 { Wx7Linux联盟 isInAsyncPostBackFieldInfo.SetValue( Wx7Linux联盟 ScriptManager.GetCurrent(this.Page), Wx7Linux联盟 true); Wx7Linux联盟 Wx7Linux联盟 this.Page.Error += new EventHandler(Page_Error); Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 private void Page_Error(object sender, EventArgs e) Wx7Linux联盟 { Wx7Linux联盟 // ... Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 private object _PageRequestManager; Wx7Linux联盟 Wx7Linux联盟 private object PageRequestManager Wx7Linux联盟 { Wx7Linux联盟 get Wx7Linux联盟 { Wx7Linux联盟 if (this._PageRequestManager == null) Wx7Linux联盟 { Wx7Linux联盟 this._PageRequestManager = pageRequestManagerPropertyInfo.GetValue( Wx7Linux联盟 ScriptManager.GetCurrent(this.Page), null); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 return this._PageRequestManager; Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 // ... Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 这段实现并不复杂。如果Request Body中的“__AjaxFileUploading__”的值为“__IsInAjaxFileUploading__”,我们就会使用反射修改ScirptManager控件中的私有变量“_isInAsyncPostBack”。此后,我们使用了自己定义的Page_Error方法来监听页面的Error事件,当页面的Error事件被触发时,我们定义的新方法就会将能够正确解析的内容发送给客户端端。 Wx7Linux联盟 Wx7Linux联盟 自然,AjaxFileUploadHelper也需要将程序集中内嵌的脚本文件注册到页面中。我为组件添加了一个开关,可以让用户开发人员使用编程的方式来打开/关闭对于AJAX文件上传的支持。这部分实现更为简单: Wx7Linux联盟 Wx7Linux联盟 注册脚本文件 Wx7Linux联盟 public bool SupportAjaxUpload Wx7Linux联盟 { Wx7Linux联盟 get { return _SupportAjaxUpload; } Wx7Linux联盟 set { _SupportAjaxUpload = value; } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 protected override void OnPreRender(EventArgs e) Wx7Linux联盟 { Wx7Linux联盟 base.OnPreRender(e); Wx7Linux联盟 Wx7Linux联盟 if (this.isInAjaxUploading) Wx7Linux联盟 { Wx7Linux联盟 this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback)); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 if (this.Page.IsPostBack || !this.SupportAjaxUpload) return; Wx7Linux联盟 Wx7Linux联盟 if (!ScriptManager.GetCurrent(this.Page).IsInAsyncPostBack) Wx7Linux联盟 { Wx7Linux联盟 ScriptReference script = new ScriptReference( Wx7Linux联盟 "Jeffz.Web.AjaxFileUploadHelper.js", this.GetType().Assembly.FullName); Wx7Linux联盟 ScriptManager.GetCurrent(this.Page).Scripts.Add(script); Wx7Linux联盟 } Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 如果用户希望关闭对于AJAX文件上传的支持,他可以使用下面的代码将页面上AjaxFileUploadHelper控件的SupportAjaxUpload属性关闭: Wx7Linux联盟 Wx7Linux联盟 关闭AJAX上传支持 Wx7Linux联盟 AjaxFileUploadHelper.GetCurrent(this.Page).SupportAjaxUpload = false; Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 等一下,这是什么?我是指在“OnPreRender”方法中的代码: Wx7Linux联盟 Wx7Linux联盟 截获输出方式 Wx7Linux联盟 if (this.isInAjaxUploading) Wx7Linux联盟 { Wx7Linux联盟 this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback)); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 解释如下:在ScirptManager的“OnPreRender”方法执行时,页面的Render方法会被服务器端PageRequestManager类的RenderPageCallback方法替代。上面代码的作用是在“我们的”异步回送时,再次使用我们定义的方法来替换页面的Render方法。请注意之前的Page_Error方法也是我们重新定义的方法,当异步回送时遇到了未捕获的异常时会使用它来输出,请注意下面的代码: Wx7Linux联盟 Wx7Linux联盟 自定义的输出方法 Wx7Linux联盟 private void RenderPageCallback(HtmlTextWriter writer, Control pageControl) Wx7Linux联盟 { Wx7Linux联盟 AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, true); Wx7Linux联盟 Wx7Linux联盟 StringBuilder sb = new StringBuilder(); Wx7Linux联盟 HtmlTextWriter innerWriter = new HtmlTextWriter(new StringWriter(sb)); Wx7Linux联盟 renderPageCallbackMethodInfo.Invoke( this.PageRequestManager, new object[] { innerWriter, pageControl }); Wx7Linux联盟 Wx7Linux联盟 writer.Write(sb.Replace("*/", "*//*").ToString()); Wx7Linux联盟 Wx7Linux联盟 AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, false); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 private void Page_Error(object sender, EventArgs e) Wx7Linux联盟 { Wx7Linux联盟 AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, true); Wx7Linux联盟 Wx7Linux联盟 onPageErrorMethodInfo.Invoke(this.PageRequestManager, new object[] { sender, e }); Wx7Linux联盟 Wx7Linux联盟 AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, false); Wx7Linux联盟 } Wx7Linux联盟 Wx7Linux联盟 Wx7Linux联盟 究竟什么是“AjaxFileUploadUtility.WriteScriptBlock”方法呢?我们为什么要这样写?其实这么做的目的是为了兼容各种浏览器,使它们都能够正确通过iframe正确收到服务器端获得的信息。这可以说是整个项目中最有技巧的部分了,我将会使用一个部分来单独讲一下这部分的机制。 Wx7Linux联盟
Linux联盟收集整理 ,转贴请标明原始链接,如有任何疑问欢迎来本站Linux论坛讨论 |
|
|
|
|
|