在Web程序中上传文件是很常见的需求。利用HTTP协议上传文件的方式非常有限,最常见的莫过于使用<input type="file" />元素进行上传。这种上传方式会将内容使用multipart/form-data方案进行编码,并将内容POST到服务器端。使用multipart/form-data编码方式与默认的application/x-url-encoded编码方式相比,在大数据量情况下效率要高很多。
使用<input type="file" />上传文件最大的优势在于编程方便,几乎各种服务器端技术都对这种上传方式做了良好的封装,使得程序员能够直观地对客户端上传的文件进行处理。不过总体来说,这个协议并不适合做文件传输,解析数据流内容的代价相对较高,并且没有一些例如断点续传的机制来辅助,导致在上传大文件时经常会力不从心。
有朋友认为使用<input type="file" />上传文件最大的问题在于内存占用太高,由于需要将整个文件载入内存进行处理,导致如果用户上传文件太大,或者同时上传的用户太多,会造成服务器端内存耗尽。这个观点其实是错误的。对于某些服务器端的技术,例如Spring Framework,或者早期ASP.NET 1.1时,为了供程序处理,都会将用户上传的内容完全载入内存,这的确会带来问题。但是其实协议本身并没有规定服务器端应该使用何种方式来处理上传的文件。例如在现在的ASP.NET 2.0中就已经会在用户上传数据超过一定数量之后将其存在硬盘中的临时文件中,而这点对于开发人员完全透明,也就是说,开发人员可以像以前一样进行数据流的处理。
ASP.NET 2.0启用硬盘临时文件的阈值(threshold)是可配置的:
<system.web>
<httpRuntime
maxRequestLength="Int32"
requestLengthDiskThreshold="Int32" />
</system.web>
maxRequestLength自不必说,刚接触ASP.NET的朋友总会发现上传文件不能超过4M,这就是因为maxRequestLength的大小默认为4096,这就限制着每个请求的大小不得超过4096KB。这么做的目的是为了保护应用程序不受恶意请求的危害。当请求超过maxRequestLength之后,ASP.NET处理程序将不会处理该请求。这里和ASP.NET抛出一个异常是不同的,这就是为什么如果用户上传文件太大,看到的并非是ASP.NET应用程序中指定的错误页面(或者默认的),因为ASP.NET还没有对这个请求进行处理。requestLengthDiskThreshold就是刚才所提到的阈值,其默认值为256,即一个请求内容超过256KB时就会启用硬盘作为缓存。这个阈值理论上和客户端是否是在上传内容无关,只要客户端发来的请求大于这个值即可。因此,在ASP.NET 2.0中服务器的内存不会因为客户端的异常请求而耗尽。
如果我们需要在ASP.NET(如果没有特别说明,以下ASP.NET均指ASP.NET 2.0)应用中上传文件,我们一般就会直接使用<asp:FileUpload />控件进行文件上传。如果一个页面中存在<asp:FileUpload />控件,那么页面中form元素的enctype就会被自动改为multipart/form-data,而且我们可以在页面PostBack之后通过<asp:FileUpload />控件的引用来获得客户端通过该控件所上传得文件。不过,如果上传文件的功能需要较为特别的需求——例如需要进度条提示,<asp:FileUpload />控件就无能为力了。
确切地说,应该是<input type="file" />所能提供的支持非常有限,因此一些特殊需求我们不能实现——严格说来,应该是无法轻易地、直接地实现。这样,在实现这些功能时,我们就会绕一个大大的弯。为了避免每次实现相同功能时都要费神费时地走一遍弯路,因此出现了各种上传组件。上传组件提供了封装好的功能,使得我们在实现文件上传功能时变得轻松了很多。例如几乎所有的上传组件都直接或间接地提供了进度提示的功能,有的提供了当前的百分比数值,有的则直接提供了一套UI;有的组件只提供了简单的UI,有的却提供了一整套上传、删除的管理界面。此外,有的组件还提供了防止客户端恶意上传的能力。
关于ASP.NET下的上传组件,最广为流传的方式莫过于在ASP.NET Pipeline的BeginRequest事件中截获当前的HttpWorkerRequest对象,然后直接调用其ReadEntityBody等方法获取客户端传递过来的数据流,并加以分析和处理。在ASP.NET 1.1时期,这么做的目的是为了直接将数据写入硬盘,以避免上传内容消耗太多服务器内存,但是现在自然已经不会因为这个原因而这么做了。从客户端发起请求到一定规模的数据传输完毕需要一段时间,那么从HttpWorkerRequest对象中读取数据流自然需要一段时间,而在这段时间内,客户端可以使用新的请求进行轮询来获得当前上传的状况。这就是获得上传进度的最传统的做法。这个做法的原理很容易理解,但是写出一个完整的组件其实很不容易,尤其是各种细节方面的问题会让人感到防不胜防。此类组件中最成功且最著名的莫过于NeatUpload了。
NeatUpload是一个开源组件,使用LGPL(Lesser General Public License)许可协议,也就是说它是“business-friendly”的。NeatUpload可以在ASP.NET和mono中使用,能够将上传的文件存在硬盘中或者Sql Server数据库中。NeatUpload提供了两个服务器控件:<NeatUpload:InputFile>和<NeatUpload:ProgressBar>。前者用于代替<asp:FileUpload />,可以通过它访问到用户通过特定上传框上传的内容;后者则是一个进度条显示控件,负责使用弹出窗口或内联的形式显示上传的进度。弹出窗口自不必说,而所谓的“内联”方式其实只是在页面中嵌入一个Iframe元素,然后通过不断刷新iframe中的页面来进行进度展示而已——可见它和弹出窗口显示方式的区别仅仅在页面所处的位置。当然,如果我们希望将其移植为AJAX形式也不难,只需开发一个页面,继承NeatUpload提供的ProgressPage类,并通过ProgressPage所提供的一些属性(总字节数,已上传字节数,已花时间,etc.)来获得当前上传的进度,最后直接使用Response.Write输出JSON形式的数据即可。事实上原本在iframe(或新窗口)中的页面,也是继承了ProgressPage类,并且使用HTML的方式进行呈现而已,本质上并没有太大区别。
不过个人认为,其实NeatUpload的实用价值不高(这点稍后再述),它最大的意义还在于提供了一个完整的优秀的示例。NeatUpload设计精巧,注释完整,是个不可多得学习案例。如果能够将NeatUpload的代码研究一遍,那么相信在编程能力和ASP.NET的理解上都会上一个新的台阶。此外,在NeatUpload站点上还能够发现NeatHtml。NeatHtml是一个开源的Web组件,用于显示不安全的内容(主要是用户输入内容,例如博客评论,论坛帖子等等),主要用于避免跨站脚本(XSS,Cross-Site Scripting)等安全问题。作为组件的作者,Dean还将NeatHtml所用到的技术总结为一篇Whitepaper,感兴趣的朋友可以看一下,这是一份不可多得的技术资料。
顺便提一下,个人认为目前很多开发人员的编程能力还不够,似乎很多人都过早地把精力放在了“设计”,或者某个特定的技术上,而忽略了最基础的“编程能力”,也就是将一段思路转化为代码实现的能力。我发现,很多朋友在解决问题的时候,似乎都能很快得到解决方案并且叙述出来,但是真正要使用代码来表现出来时却显得困难重重。其实在工作中,思路或解决方案可以通过讨论而获得,但是真正转化为代码的时候只能靠自己了。而且编程能力其实和所谓的“工作经验”无关,我建议以“应届毕业生”“自居”的朋友,可以定心地锻炼一下自己的编程能力。
与NeatUpload类似的开源组件还有Memba Velodoc XP Edition,它是Velodoc文件管理系统的核心。不过严格说来,这不仅仅是一个上传组件,而是一套文件管理的解决方案,它包含:
- 一个兼容IIS 7集成管道模式的ASP.NET Http Module,支持大文件上传使用(有趣的是,NeatUpload申明,IIS 7的一个Bug使它无法在IIS 7集成管道模式中使用)。
- 一个支持断点续传的ASP.NET Http Handler。
- 一系列ASP.NET服务器端控件,提供了文件上传功能所需的UI,包括一个多文件上传控件,一个ListView控件和一个进度条控件。
- 一个Web应用程序,可以替换FTP的交换文件方式,支持Email发送链接。它也是上面所提到的组件的使用示例。
- 一个Windows Service,用于定期清理旧文件。
- 一个测试项目、一个部署项目、以及一个安装项目。
- 文档。
回到NeatUpload组件。说实话,我始终不喜欢这种进度获取方式,因为我觉得通过一个额外的请求对服务器进行轮询无疑是一个累赘。事实上,如果需要上传大文件并且获得上传进度,目前最好的方式应该是使用RIA方式。最典型的RIA上传方式就是利用Flash了。ActionScript 2.0中已经存在FileReference和FileReferenceList组件以支持单文件和多文件的上传,有了这两个组件,上传的各种信息已经能够完全在客户端获得,而上传进度也自然能够计算出来。FileReference和FileReferenceList组件非常容易使用,就连像我这样对Flash一窍不通的人,也能在短时间内作出一个简单的上传功能。但是自从有了swfupload,世界就变得更美好了。
严格说来,通过FileReference所得到的上传进度是“客户端发送数据的进度”,而像NeatUpload的做法得到的是“服务器端接受数据的进度”,两者不可混为一谈。
swfupload也是个开源组件,顾名思义是使用Flash进行上传。不过对于swfupload来说,Flash的作用主要是“控制”,而不是“展示”,这无疑给了开发人员更大的灵活性。swfupload的实现方式自然是利用了FileReference和FileReferenceList组件所提供的功能,通过Flash与JavaScript的交互能力,使得开发文件上传功能变得非常优雅和容易。有了swfupload,开发人员可以使用JavaScript来实现各种显示方式,开发像Flicker一样酷酷的上传界面也不再是非常困难的事情了。
swfupload是个客户端组件,它对于服务器端来说完全透明,也就是说,服务器端只需要使用对待普通form的方式来处理即可。例如在ASP.NET中我们可以使用Generic Handler来处理客户端的文件上传。如下,fileCollection变量即为客户端Post至服务器端所有文件的集合,我们可以使用name或下标的方式来获得其中的HttpPostedFile对象。:
public class UploadHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
HttpFileCollection fileColllection = context.Request.Files;
...
}
public bool IsReusable { ... }
}
既然Flash提供了文件上传功能,Silverlight作为微软主推的RIA技术也不会缺了这项功能。这篇文章源自Silverlight 2.0的Quick Starts,展示了如何使用Silverlight 2.0开发文件上传的功能,感兴趣的朋友可以一读。
围绕着ASP.NET中上传文件这个话题也讨论了不少了,还有什么没有涉及到的吗?个人认为其实至少还有一个非常重要问题是没有讨论过,那就是在处理上传文件时占用ASP.NET处理线程的问题。众所周知,ASP.NET处理请求时会用到线程池中的线程,当线程池中的线程被用完之后没有被处理的请求只能排队了。因此增大ASP.NET应用程序吞吐量的一个重要手段,就是为一些耗时的操作使用异步处理方式(事实上这一命题可以在大部分应用中成立)。例如一个数据库查询操作需要3秒钟,如果不使用异步操作,处理线程就会被阻塞,直至查询完成。如果使用异步方式来执行数据库查询,在这3秒钟内线程就可以用户处理其他请求,当异步操作结束之后,ASP.NET就会使用另一个线程来继续处理这个请求。
上传大文件也是一个长时间占用处理线程的工作,而且遗憾的是,这无法使用异步操作来完成(通过异步操作来释放处理线程需要操作系统的支持,因此只有少量功能可以使用异步操作)。如果一个文件上传需要3分钟时间,那么在这3分钟内就会独占一个处理线程,如果上传文件的连接一多,就会大大影响应用程序的性能——就像遭受了某种方式的DOS攻击一样。因此,即使使用了像NeatUpload和swfupload这样的组件,也无法解决上传连接过多造成可用线程减少的问题。要解决这个问题并不容易,以下是两种思路(欢迎大家就此问题进行讨论):
- 扩展IIS,使上传文件或处理文件的过程不经ASP.NET处理,以减少ASP.NET应用程序线程的消耗。现在有了IIS 7,如果使用集成管道模式,应该也可以使用托管代码进行扩展。
- 使用额外的ASP.NET应用程序处理文件上传,以节省上传文件的线程对原ASP.NET应用程序线程的消耗。
就先说到这里吧。
分享到:
相关推荐
这部分内容不需要读者有任何的客户端开发知识,只要在Visual Stuio中轻松拖放即可实现强大的客户端Ajax功能,例如局部页面更新、异步回送、拖放、动画等,非常适合为现有的ASP.NET 2.0应用程序添加少量的Ajax特性,...
ASP.NET是微软公司推出的一种用于构建Web应用程序的框架,它基于.NET Framework,为开发者提供了丰富的功能和工具,简化了Web应用程序的开发流程。本教程旨在深入解析ASP.NET应用程序的开发,通过程序清单的方式,...
在ASP.NET应用程序开发过程中,了解并熟悉各种文件类型对于构建高效、可维护的应用程序至关重要。以下是对ASP.NET应用中常见的文件类型的详细介绍: ### 1. C#源代码文件(.cs) C# 是 ASP.NET 应用中最常用的编程...
web.config文件是一个ASP.NET应用程序的配置文件,能够帮助我们配置文件上传的功能。在本文中,我们使用web.config文件来配置文件上传的最大值。 知识点8:使用MSDN查看文件上传的限制 在本文中,我们使用MSDN来...
* Web 应用程序:ASP.NET Core 适用于开发各种 Web 应用程序,包括商业应用程序、社交媒体应用程序、游戏应用程序等。 * 移动应用程序:ASP.NET Core 也适用于开发移动应用程序,使用 Xamarin.Forms 或其他跨平台...
ASP.NET 2.0是微软开发的一个用于构建Web应用程序的框架,它是在.NET Framework 2.0版本上运行的。这个“ASP.NET 2.0程序设计案例教程”旨在为学习者提供深入理解和实践ASP.NET技术的机会。通过课件的形式,我们可以...
构建ASP.NET应用程序是一个涵盖多个关键领域的主题,包括Web Forms、ASP.NET应用程序开发、Web应用程序安全性、配置与监控、从ASP的迁移以及Starter Kits的介绍。以下是对这些知识点的详细说明: 1. **Web Forms**...
ASP.NET版的小程序微信支付接口文件是用于在ASP.NET平台上集成微信支付功能的关键组件,它使得开发者能够方便地在自己的小程序应用中实现安全、便捷的在线支付。此接口文件包括了必要的配置信息以及处理支付请求的...
- 文件上传与下载:实现在ASP.NET应用中处理文件上传和下载的功能。 - 实时通信:了解SignalR或其他技术,实现Web应用的实时更新和推送。 本书旨在帮助开发者从零开始掌握ASP.NET技术,逐步提升为熟练的Web应用...
ASP.NET Web程序设计是微软开发的一种用于构建动态网站、Web应用程序和Web服务的技术。这个电子教案专注于使用C#编程语言进行开发,以下是根据压缩包文件的名称所涵盖的知识点的详细说明: 1. **第一章 ASP.NET概述...
6. **异常处理**:介绍如何在ASP.NET中有效地捕获和处理异常,确保应用程序的稳定性和用户体验。 7. **安全与身份验证**:讨论ASP.NET的安全特性,如身份验证(Forms Authentication)、授权(Role-Based ...
翻译:使用 Asp.net mvc 15 分钟创建 Movie 数据库应用程序
1. ASP.NET:ASP.NET是.NET框架的一部分,用于构建动态网站、Web应用和服务。它提供了多种模型,如Web Forms、MVC和Blazor,本项目可能基于Web Forms,这是一个事件驱动的模型,允许开发者创建服务器端控件并处理...
在这个“ASP.NET 2.0快速入门(14)”教程中,我们将深入探讨如何使用ASP.NET 2.0来管理和维护你的ASP.NET应用程序。 1. **配置管理工具**: ASP.NET 2.0引入了一个强大的配置管理工具,允许开发者在不重启应用程序...
ASP.NET Web程序设计是微软公司推出的用于构建动态网站、Web应用程序和Web服务的框架,它基于.NET Framework,为开发者提供了一种高效、安全且可扩展的环境来开发Web项目。本电子教案由王祖俪编写,旨在深入浅出地...
在ASP.NET程序设计中,C#是首选的编程语言,因为它提供了丰富的特性和强大的面向对象支持。 本资源“asp.net程序设计(C#)版课后题答案”似乎是一个针对学习者或初学者的资料,包含了对ASP.NET课程中第7章课后习题...
在ASP.NET Web API中处理文件上传是常见的需求,特别是当你需要允许用户通过Web界面提交文件时。本文将深入探讨如何在ASP.NET Web API中实现文件上传功能,并涉及MIME多部分类型。 首先,让我们了解MIME...
ASP.NET是一种由微软开发的服务器端Web应用程序框架,用于构建动态、数据驱动的Web应用程序。这个实例程序集合包含了使用C#和VB(Visual Basic)编程语言以及数据库技术的示例代码,旨在帮助开发者更好地理解和应用...
同时,实验还引导学生了解如何在ASP.NET环境中创建项目文件,这是构建ASP.NET应用程序的第一步。 实验3则深入到了ASP.NET的基本服务器控件的使用。这部分实验旨在教授学生如何利用ASP.NET提供的服务器控件来增强...
12. **部署和调试**:了解如何在IIS上部署ASP.NET应用程序,以及使用Visual Studio进行调试,是开发过程中不可或缺的技能。 在《ASP.NET 程序设计基础篇.pdf》中,你可能会学习到这些概念的详细解释,包括实例代码...