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

  目前使用的解决方案是,我们在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;
}
} // ...
}

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

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

注册脚本文件

public bool SupportAjaxUpload
{
get { return _SupportAjaxUpload; }
set { _SupportAjaxUpload = value; }
} protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e); if (this.isInAjaxUploading)
{
this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback));
} if (this.Page.IsPostBack || !this.SupportAjaxUpload) return; if (!ScriptManager.GetCurrent(this.Page).IsInAsyncPostBack)
{
ScriptReference script = new ScriptReference(
"Jeffz.Web.AjaxFileUploadHelper.js", this.GetType().Assembly.FullName);
ScriptManager.GetCurrent(this.Page).Scripts.Add(script);
}
}

  如果用户希望关闭对于AJAX文件上传的支持,他可以使用下面的代码将页面上AjaxFileUploadHelper控件的SupportAjaxUpload属性关闭:

关闭AJAX上传支持

AjaxFileUploadHelper.GetCurrent(this.Page).SupportAjaxUpload = false;

  等一下,这是什么?我是指在“OnPreRender”方法中的代码:

截获输出方式

if (this.isInAjaxUploading)
{
this.Page.SetRenderMethodDelegate(new RenderMethod(this.RenderPageCallback));
}

  解释如下:在ScirptManager的“OnPreRender”方法执行时,页面的Render方法会被服务器端PageRequestManager类的RenderPageCallback方法替代。上面代码的作用是在“我们的”异步回送时,再次使用我们定义的方法来替换页面的Render方法。请注意之前的Page_Error方法也是我们重新定义的方法,当异步回送时遇到了未捕获的异常时会使用它来输出,请注意下面的代码:

自定义的输出方法

private void RenderPageCallback(HtmlTextWriter writer, Control pageControl)
{
AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, true); StringBuilder sb = new StringBuilder();
HtmlTextWriter innerWriter = new HtmlTextWriter(new StringWriter(sb));
renderPageCallbackMethodInfo.Invoke(
this.PageRequestManager, new object[] { innerWriter, pageControl }); writer.Write(sb.Replace("*/", "*//*").ToString()); AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, false);
} private void Page_Error(object sender, EventArgs e)
{
AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, true); onPageErrorMethodInfo.Invoke(this.PageRequestManager, new object[] { sender, e }); AjaxFileUploadUtility.WriteScriptBlock(this.Page.Response, false);
}

  究竟什么是“AjaxFileUploadUtility.WriteScriptBlock”方法呢?我们为什么要这样写?其实这么做的目的是为了兼容各种浏览器,使它们都能够正确通过iframe正确收到服务器端获得的信息。这可以说是整个项目中最有技巧的部分了,我将会使用一个部分来单独讲一下这部分的机制。

让UpdatePanel支持文件上传(2):服务器端组件 .的更多相关文章

  1. 让UpdatePanel支持文件上传(1):开始 .

    UpdatePanel从一开始就无法支持AJAX的文件上传方式.Eilon Lipton写了一篇文章解释了这个问题的原因.文章中提供了两个绕开此问题的方法: 将“上传”按钮设为一个传统的PostBac ...

  2. RPC基于http协议通过netty支持文件上传下载

    本人在中间件研发组(主要开发RPC),近期遇到一个需求:RPC基于http协议通过netty支持文件上传下载 经过一系列的资料查找学习,终于实现了该功能 通过netty实现文件上传下载,主要在编解码时 ...

  3. Openresty + nginx-upload-module支持文件上传

    0. 说明 这种方式其实复杂,麻烦!建议通过这个方式搭建Openresty文件上传和下载服务器:http://www.cnblogs.com/lujiango/p/9056680.html 1. 包下 ...

  4. java nio 写一个完整的http服务器 支持文件上传 chunk传输 gzip 压缩 使用过程 和servlet差不多

    java nio 写一个完整的http服务器  支持文件上传   chunk传输    gzip 压缩      也仿照着 netty处理了NIO的空轮询BUG        本项目并不复杂 代码不多 ...

  5. 让nginx支持文件上传的几种模式

    文件上传的几种不同语言和不同方法的总结. 第一种模式 : PHP 语言来处理 这个模式比较简单, 用的人也是最多的, 类似的还有用 .net 来实现, jsp来实现, 都是处理表单.只有语言的差别, ...

  6. springmvc学习笔记--支持文件上传和阿里云OSS API简介

    前言: Web开发中图片上传的功能很常见, 本篇博客来讲述下springmvc如何实现图片上传的功能. 主要讲述依赖包引入, 配置项, 本地存储和云存储方案(阿里云的OSS服务). 铺垫: 文件上传是 ...

  7. winform程序压缩文件上传,服务器端asp.net mvc进行接收解压

    期间编程没什么难度,唯一可能忽略导致结果失败是asp.net  mvc配置 对于压缩文件大的话,需要配置mvc的最大接收量: <system.web> <httpRuntime ma ...

  8. 配置servlet支持文件上传

    Servlet3.0为Servlet添加了multipart配置选项,并为HttpServletRequest添加了getPart和getParts方法获取上传文件.为了使Servlet支付文件上传需 ...

  9. java支持断点续传文件上传和下载组件

    java两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路: 1.服:利用ServerSocket搭建服务器,开启相应端口,进行长连接 ...

随机推荐

  1. 【JAVA-WEB】在url上追加sessionid

    HttpSession session = request.getSession(); url = url+";jsessionid="+session.getId();

  2. Determining IP information for eth0...failed

    事故现场 eth0 Link encap:Ethernet HWaddr :0C::B6:D2:5A inet6 addr: fe80::20c:29ff:feb6:d25a/ Scope:Link ...

  3. python爬虫的教程

    来源:http://cuiqingcai.com/1052.html 大家好哈,我呢最近在学习Python爬虫,感觉非常有意思,真的让生活可以方便很多.学习过程中我把一些学习的笔记总结下来,还记录了一 ...

  4. orcale 之 PL/SQL的游标

    根据我们之前了解到的情况,SQL是面向集合的,我们的查询结果一般包含多条数据,而在PL/SQL 中的变量一般只能存放一条数据,因此变量是无法满足我们的需求的.这时候我们就需要引入游标来为我们解决问题了 ...

  5. svn 被锁,清理恢复过程

    svn 被锁,清理恢复过程 http://stackoverflow.com/questions/18000363/tortoisesvn-wont-allow-me-to-add-any-files ...

  6. 群晖MyDS账号注册--实现使用QuickConnect外网访问

    最近公司拿了个NAS给我,让我把它配置好,之前没有接触过这个东西,上网一查,发现就是和去年很火的玩客云和斐讯天天链N1的功能一样,可以实现文件储存和文件共享. 设备型号:群晖DS214SE 系统版本: ...

  7. javaweb 实现跨域

    现在的一个web应用会涉及到多个地方的restAPi的调用,传统的jsonp虽然支持跨域,但是只是支持get请求. 传统的ajax请求是不支持跨域的,是为了安全考虑. 跨域的思路是跟http机制有关, ...

  8. assert函数的用法

    assert这个函数在php语言中是用来判断一个表达式是否成立.返回true or false; 例如: <?php $s = 123; assert("is_int($s)" ...

  9. 关于对Enum的理解

    之前一直对枚举类型的理解存在误解,现重新学习 Enum 类型的介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则 ...

  10. 747_Largest-Number-At-Least-Twice-of-Others

    目录 747_Largest-Number-At-Least-Twice-of-Others Description Solution Java solution Python solution 74 ...