随便说说:在ASP.NET应用程序中上传文件

在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文件管理系统的核心。不过严格说来,这不仅仅是一个上传组件,而是一套文件管理的解决方案,它包含:
  1. 一个兼容IIS 7集成管道模式的ASP.NET Http Module,支持大文件上传使用(有趣的是,NeatUpload申明,IIS 7的一个Bug使它无法在IIS 7集成管道模式中使用)。
  2. 一个支持断点续传的ASP.NET Http Handler。
  3. 一系列ASP.NET服务器端控件,提供了文件上传功能所需的UI,包括一个多文件上传控件,一个ListView控件和一个进度条控件。
  4. 一个Web应用程序,可以替换FTP的交换文件方式,支持Email发送链接。它也是上面所提到的组件的使用示例。
  5. 一个Windows Service,用于定期清理旧文件。
  6. 一个测试项目、一个部署项目、以及一个安装项目。
  7. 文档。
回到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对象。:
publicclass 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应用程序线程的消耗。
就先说到这里吧。


本文转自 jeffz 51CTO博客,原文链接:http://blog.51cto.com/jeffz/61888,如需转载请自行联系原作者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/392628.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 148. 排序链表(归并排序)

给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 进阶&#xff1a; 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下&#xff0c;对链表进行排序吗&#xff1f; 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1…

理论与哲学就是梳理无限感性经验和知性知识的工具

理论与哲学就是梳理无限感性经验和知性知识的工具&#xff0c; 是因为人脑的特性&#xff0c;或者说人脑功能的局限性而被人创造出来的思想工具。 工具服务于实践&#xff0c;并被实践所检验&#xff08;描述、解释、预见&#xff09;。 工具一旦创建就有其客观性&#xff0c;但…

记一次调用API遇到的问题

调用sonar API 接口&#xff0c;地址如下&#xff1a; http://sonar.*****.com/api/measures/search_history?componenttcmp-devops-service&metricssqale_index%2Cduplicated_lines_density%2Cncloc%2Ccoverage%2Cbugs%2Ccode_smells%2Cvulnerabilities&ps1000 url上…

h5滚动隐藏滚动条_这 10 个值得开启的隐藏功能,让你的 Chrome 释放更多潜力

上次分享了让 Chrome 浏览器用得更顺手的地址栏命令&#xff0c;跟大家整理和介绍了多个 Chrome 地址栏命令&#xff0c;利用好这些命令工具能够提升浏览器配置效率&#xff0c;让你的 Chrome 浏览器用得更顺手。这次介绍的是 Chrome 内置的实验功能&#xff0c;它被单独放在了…

redux ngrx_另一个减少Redux(NGRX)应用程序样板的指南

redux ngrxby Andrey Goncharov通过安德烈贡恰洛夫(Andrey Goncharov) 另一个减少Redux(NGRX)应用程序样板的指南 (Yet another guide to reduce boilerplate in your Redux (NGRX) app) 我们在这里要覆盖什么&#xff1f; (What are we gonna cover here?) In this article,…

leetcode 242. 有效的字母异位词

给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输入: s “rat”, t “car” 输出: false 代码 class Solution {public boolean isAnagram(String s, String t) {…

技巧:使用User Control做HTML生成

User Control大家肯定不会陌生&#xff0c;在使用ASP.NET的过程中&#xff0c;除了aspx页面&#xff0c;最常见的就莫过于ascx了。ascx是一个有独立逻辑的组件&#xff0c;提供了强大的复用特性&#xff0c;合理使用&#xff0c;能够大大提高开发效率。通过User Control直接生成…

Spring Boot干货系列:(二)配置文件解析

前言 上一篇介绍了Spring Boot的入门&#xff0c;知道了Spring Boot使用“习惯优于配置”&#xff08;项目中存在大量的配置&#xff0c;此外还内置了一个习惯性的配置&#xff0c;让你无需手动进行配置&#xff09;的理念让你的项目快速运行起来。所以&#xff0c;我们要想把S…

mysql常用操作记录

&#xff08;1&#xff09;判断表中一个字段是空&#xff0c;可为&#xff1a;字段名 IS NULL&#xff08;2&#xff09;类似oracle的decode作用&#xff1a;IF(字段名>0,字段名,0)&#xff08;3&#xff09;时间格式&#xff08;年-月-日&#xff09;&#xff1a;DATE_FORM…

小爱音响调用php接口_阿里API调用二(PHP)

接口地址拼合成功后&#xff0c;用curl函数post获取阿里返回的完整数据&#xff0c;将地址传入getContent()方法中&#xff0c;绝对能获取用户数据。public function getContent($url){$ch curl_init();// 初始化curl_setopt($ch,CURLOPT_URL,$apiprourlall);curl_setopt($ch,…

leetcode 452. 用最少数量的箭引爆气球(贪心算法)

在二维空间中有许多球形的气球。对于每个气球&#xff0c;提供的输入是水平方向上&#xff0c;气球直径的开始和结束坐标。由于它是水平的&#xff0c;所以纵坐标并不重要&#xff0c;因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 一支弓箭可以沿着 x…

javascript编程题_如何开始使用JavaScript进行竞争性编程

javascript编程题by Priyabrata Biswas通过Priyabrata Biswas 如何开始使用JavaScript进行竞争性编程 (How to get started with Competitive Programming in JavaScript) If you’re not familiar with competitive programming, basically it is a mind sport with the aim …

hibernate Criteria(条件查询接口)

Criteria&#xff08;条件查询接口&#xff09; // 1.简单查询 List<Customer> list session.createCriteria(Customer.class).list();// 2.条件查询: Criteria criteria session.createCriteria(Customer.class); criteria.add(Restrictions.eq("name",&quo…

ElastciSearch简单总结(笔记)

前言&#xff1a; 前段时间在项目中使用了es,作为一个当前比较流行的分布式搜索引擎&#xff0c;在学习和使用它的过程中&#xff0c;踩了不少坑&#xff0c;这篇文章先简单整理了一下&#xff0c;后续会整理一下之前踩过的一些坑。 1. ElastciSearch是什么 ElasticSearch是一…

记一次ArrayList产生的线上OOM问题

前言&#xff1a;本以为(OutOfMemoryError)OOM问题会离我们很远&#xff0c;但在一次生产上线灰度的过程中就出现了Java.Lang.OutOfMemoryError:Java heap space异常&#xff0c;通过对线上日志的查看&#xff0c;最终定位到ArrayList#addAll方法中&#xff0c;出现这个问题的原…

leetcode 222. 完全二叉树的节点个数(dfs)

给出一个完全二叉树&#xff0c;求出该树的节点个数。说明&#xff1a;完全二叉树的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位置。若最底…

css 计算属性的应用_如何使用一点CSS Grid魔术设计计算器应用

css 计算属性的应用by Deepika Gunda由Deepika Gunda 如何使用一点CSS Grid魔术设计计算器应用 (How to use a little CSS Grid magic to design a calculator app) This article is a quick intro to CSS Grid. We will be making a calculator using it.本文是CSS Grid的快速…

vc调试大全

一、调试基础 调试快捷键 F5&#xff1a; 开始调试 ShiftF5: 停止调试 F10&#xff1a; 调试到下一句&#xff0c;这里是单步跟踪 F11&#xff1a; 调试到下一句&#xff0c;跟进函数内部 ShiftF11: 从当前函数中跳出 CtrlF10: 调试到光标所在位置 F9&#xff1a; …

Google-Guava-EventBus源码解读

Guava是Google开源的一个Java基础类库&#xff0c;它在Google内部被广泛使用。Guava提供了很多功能模块比如&#xff1a;集合、并发库、缓存等&#xff0c;EventBus是其中的一个module&#xff0c;本篇结合EventBus源码来谈谈它的设计与实现。 概要 首先&#xff0c;我们先来预…

leetcode 1370. 上升下降字符串

给你一个字符串 s &#xff0c;请你根据下面的算法重新构造字符串&#xff1a; 从 s 中选出 最小 的字符&#xff0c;将它 接在 结果字符串的后面。 从 s 剩余字符中选出 最小 的字符&#xff0c;且该字符比上一个添加的字符大&#xff0c;将它 接在 结果字符串后面。 重复步骤…