以前开发过一个用Asp.NET上传大文件的代码,由于是在NET1.1下开发的,发现不能在NET.20下运行,前几天将这个问题解决了,现在把整个开发过程整理一下,供大家参考。由于内容较多,所以我打算分成六篇来写,第一篇写一下需要解决的问题和大致解决思路,再用四篇具体写一下开发中需要解决的四个主要问题,最后再写一个组件的使用方法。
大家可以点击这里下载NET1.1的源代码。
使用Asp.NET在上传文件时,IIS接收到请求内容后,发送到Asp.NET的管道中,然后Asp.NET的ISAPI将读取的内容是全部装入服务器内存(NET2.0在请求内容较大时,会存放到临时文件中,见后面的开发说明),在上传文件时,消耗服务器资源较多,所以Asp.NET对上传文件的大小会有限制,且不能提供上传进度显示。而我们在实际应用中,往往会上传较大的文件的现象,这时,可以通过修改配置文件将最大将求长度配置设置得很大,但这样,服务器将会消耗大量内存。
为此,如果可以在IIS将客户端发送的内容发送到Asp.NET的管道时,随着服务器接收内容的同时,将文件内容写到服务器磁盘中,然后在 Asp.NET的管道中只放入请求的对应的Form内容。这样可以避开Asp.NET上传文件的大小限制,同时提供服务器接收进度显示。
Asp.NET提供了HttpModule功能,提供了让用户处理客户请求的手段,要实现HttpModule功能,用户只需实现 System.Web.IHttpModule接口。System.Web.IHttpModule定义了两个方法void Init (HttpApplication context)和void Dispose (),Init方法给我们提供了一个类型为HttpApplication的参数,这个HttpApplication类型中,给我们提供了处理 Asp.NET页面生命周期中发生的各种事件(具体请参照MSDN),我们只需要在最早的事件BeginRequest中,处理数据接收,就可以避开Asp.NET的最大请求长度限制。
为了不改变Asp.NET对页面处理逻辑,我们需要在提供文件内容后,将其它请求内容正确封装给Asp.NET,为此我们需要分析客户端发送到服务器端内容的具体格式,我们可以通过其它工具,提供这些内容,这些内容大致如下:
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="UploadID"
de858d87-e8b8-4f7a-a832-357da2efdf13
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="__VIEWSTATE"
/wEPDwULLTIwODE1OTkzNTUPZBYCAgMPFgIeB2VuY3R5cGUFE211bHRpcGFydC9mb3JtLWRhdGFkZAQ0pcd9kiSZ7/MOe0enKOTGFxMk
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="TextBox1"
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="TextBox2"
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="file1"; filename="C:/Documents and Settings/Administrator/??????/componentart.web.ui.rar"
Content-Type: application/x-rar-compressed
Rar! ??s这里我把文件具体内容去掉了,我们提取文件后,封送给Asp.NET也就是去掉这部分后的内容。
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="Button1"
开始上传
-----------------------------7d81e441d025c
Content-Disposition: form-data; name="__EVENTVALIDATION"
/wEWBAL1w7VGAuzRsusGAuzR9tkMAoznisYGMJIxZyapejeOQaIIwcOkPrWK6nE=
-----------------------------7d81e441d025c--
请注意红色注释部分,那里原来是上传的文件内容,我们封送给Asp.NET就应当时去掉文件内容后内容,这样,页面控件的值、页面事件就会正确的发送到Asp.NET页面。只是页面不能正确获取上传文件的内容。
这里又出现了三个问题:一是:我们如何获取文件内容,如何确定那部分内容是上传的文件内容而不是页面其它控件内容;二是:我们如何在提取文件内容后让 Asp.NET页面正确获取文件;三是:我们如何将改写后的内容封送给Asp.NET,让它感觉不到我们提取了文件内容。
只要解决了上面三个问题,那大文件上传的问题就解决了,最后就是提供进度显示,由于内容较多,我分四部分来介绍这三个问题和进度显示。
待续...
不知地震什么时候结束,为了给老婆小孩守夜,看来还不能睡,那就把第二篇也写了吧,只是不知对大家有没有用哟。
为了提供文件内容,我们需要首先需确定客户请求中发送的有文件内容,然后确定文件内容的位置。这部分对应的代码如下:
然后我们采用这个方法,就可以从请求的内容中提取出类似“-----------------------------7d81e441d025c”这样的字符串。这样,我们就可以分隔页面内不同控件的内容了。
最后,我们分析文件上传控件的内容:
我们可以发现,文件上传控件有一个filename属性,然后,下一行指定文件的MIME类型,随后这个空白行,在空白行下面,直到下一处类似“-----------------------------7d81e441d025c”标志字符串间,就是客户端发送的文件内容。我们只需把这些内容写入到服务器文件中即可。
这里,文件内容的提取的问题就可以解决了。在提取文件内容时,对处理标志字符串时需要特别处理,防止标志字符分别读取到两个缓冲区中,所以这部分代码还是比较多的,具体请参看源代码中的DataReader类。
第二篇就写到这,实在是太困了,后面的等一下再写吧!
这一篇相对就要简单一些了,只需要确定对文件的处理思路和与Asp.NET页面信息的传递的问题。
.NET1.1中,Asp.NET将获取的请求内容放入到一个Byte[]类型中,但在Asp.NET2.0中,却将数据封装到一个类型为 System.Web.HttpRawUploadedContent的对象中,在这个新对象中,有一个重要的属性:_file,这个属性的类型为 TempFile。正好是这个类型提供了将请求内容保存到临时文件的功能。这些信息都可能通过Reflector工具反编译获得(所以啊,做.NET开发,这个工具是必备的)。
在HttpRequest类中,我们可以发现如下用于分析页面各控件值的代码:
说了这么多,只是说明了一个问题:通过改变Asp.NET页面对象的属性值来向页面传送是比较麻烦的。所以我采取单件模式的设计思路。具体是,将获取的文件内容写入到磁盘文件;每次上传文件过程中,初始化一个会话标志,页面通过这个会话标志来提取这些文件信息。在系统中,定义了两个类用来处理这项工作:
类UploadContext用于封装文件信息。
类UploadContextFactory用于会话标志的管理。
在上传页面里,组件将在页面中注册一个名为UploadID的Hidden字段。存放一个GUID作为会话标识。这个标志会在文件上传时,出现在请求内容中,组件通过获取这个会话标志和文件内容,正确将文件保存到临时目录中,并将提取文件信息供页面使用。页面则根据Request["UploadID"]字段在类UploadContextFactory提取上传文件对应的会话标志,并获取文件信息。这样就实现了页面对文件内容的提取。
这样,要求在上传页面的Page_Load事件中,采用如下语句来初始化一个会话,并指定存放文件的目录。
在需要获取文件信息的地方:采用如果语句来获取:
当然这里就存在一个问题是:会话标志过期及没有初始化标志上传文件时的处理(如不请求页面,而直接发送文件,这可能会在用户使用其它工具发送时出现)。这个问题暂时没有处理。
现在,上传的文件可以上传到服务器了,页面也可以获取上传的文件内容以作进一步处理,如限制文件类型、存放到数据库等。
感觉思路有点乱了,那就先写出来,我随后再来整理。
这个功能主要是为了不影响Asp.NET的处理模型,将请求中除上传的文件内容外的其它正常请求内容继续发送到 Asp.NET页面处理。
我们通过分析 HttpRequest对象的 GetEntireRawContent()方法在读取数据,并且发现这个方法在读取数据时,是将数据赋值到了 _rawContent属性,所以我们只要能把处理后的数据赋值给 HttpRequest对象的 _rawContent属性,即可封送数据给 Asp.NET页面。同时,由于我们改写了请求内容,所以 HttpRequest的 _contentLength也应当改写。
由于这些属性方式是私有的,我们不能直接访问,所以我们必须采用反射的方法给属性赋值。代码如下:
这几天又出去躲地震了,本来以为没什么了,让电视里的那些专家出来一吓,害得我又出去受了几天罪,这就象网上说的:比地震更可怕的是余震,比余震更可怕的是预报余震,比预报余震更可怕预报了余震却一直不震。所以这篇就写得迟了一些,今天打算回家睡了,所以随笔也就接到写了。
首先先看看我做的上传进度信息显示效果吧:
怎么样,就点象C/S效果吧。不过这里正象前面一位网友说的:这是靠不停的查询服务器来获得进度信息的,这一点对提高系统的并发度有影响。不过,我想:使用Asp.NET上传文件的应用系统,一般都不会有太多的并发用户,之所以要用Asp.NET上传文件主要是为了提高用户体验,简化系统部署和开发吧。在一般的信息化系统使用这个组件还是很方便的。大家说呢。
前面只要把文件上传处理了,这里要从服务器获取进度信息相对就容易多了,我这里用的是弹出模态窗口,用刷新页面的方式来显示进度信息,当然也可以使用AJAX技术获取进度信息,也可以不用模态窗口,而用DIV标签或IFrame方式,不过,如果不模态窗口方式,那在文件上传完成后,这些进度信息也会自动随着页面刷新而消失。大家可以实现这几种方式,如果有朋友做出来了,别忘了给我一份哟。
对于进度的显示,大家可以下载我的源码看看就会明白,我就不多说了,我只提示一下网友需要注意的几个地方:在进度显示页面里,要设<base target="_self"> 标签,以免在IE5.0下面不能正常显示进度信息,刷新时,可能会打开新的窗口。
可以修改这里Response.AppendHeader("Refresh","3");,控制刷新频率,这里有一个效率和用户体验的权衡,刷新时间过小,会增加服务器负担,时间过大,进度条显示会呈跳跃状。
其它的请网友下载我的源代码吧,一看就会明白,我的第一篇文章里有net1.1版本的下载 ,用户可以在我的下一篇也是本系列的最后一篇下载Net2.0版本。
为了方便用户使用 大文件上传组件,特写使用说明书,以指明调用组件的接口,操作流程和注意事项。
由于 ASP.NET在上传文件时,是全部装入服务器内存,在上传文件时,消耗服务器资源较多,且不能提供上传进度显示,由于消耗服务器资源较多,所以 ASP.NET上传文件时往往大小受到限制。此组件解决了所有这些问题
大文件:大文件主要意思是指大小不受限制,
Web.config:是 ASP.NET的配置文件。
所有需要用 ASP.NET上传文件的地方,均可以使用本组件。本组件可以解除 ASP.NET对上传文件大小的限制,同时提供上传进度提示,用户还可以选择中断上传过程。减少对服务器资源的消耗(经测试,服务器资源消耗与运行普通 ASP.NET页面相差无几)
大文件上传处理模块,用于在客户端请求发送到服务器上后,分析请求内容,如果是上传文件,则在 IIS将数据出发送到 ASP.NET的管道中时,读取数据,写入文件,并重新组织请求内容,重新组织的内容是去掉了文件内容的请求内容。页面在接收到请求后,除文件上传控件不能读取文件内容外,其它组件的属性可以正常访问。
请求数据分析和文件写入。
上传文件属性结构,供组件内部使用。
文件上传环境,组件在使用时,主要使用此类。具体有以下方法和属性:
UploadContext :构造函数,需传入文件上传页面对象和文件临时存放目录。外部不能直接调用些构造函数,要构建文件上传环境,需调用 UploadContextFactory的提供的静态方法
TmepFileDir :获取和设置上传临时文件存放路径
FileNames 上传的文件名列表
TotalLength 发送信息总长度,包括页面所有控件请求内容
Readedlength 已接收的信息长度 包括面页控件请求内容
StartReadDateTime 开始接收时间
FileConIds 页面文件上传控件 ID列表,对文件上传控件在服务器端运行时,为控件的 UniqueID
GUID :上传文件唯一标志
Abort :设置和读取是否中断上传过程
Ratio 上传速率 ,返回每秒上传的字节数
FormatRatio 获取格式化的上传速度,以适当的字节, K字节, M字节表示
LeftTime 估计上传剩余时间 ,以秒为单位
FormatLeftTime 获取格式上的上传剩余时间,适当的以小时,分钟,秒表示
CurrentFile 当前正在处理的上传文件
Status 上传状态
FormatStatus 以字符串格式,返回当前上传操作的状态
SaveFile 另存上传的数据文件,此方法主要将文件从临时存放目录移到用户需要求的目录,由于采取移动文件办法,所以此方法调用成功后,临时文件对应文件将被移走。
GetFileNameByControl 根据页面文件上传控件名称,获取上传的文件名
Dispose 当前上下文环境中接收的所有文件
GetFileName :根据文件上传控件名称,获取对应的上传的文件。
创建和获取文件上传环境。
InitUploadContext(System.Web.UI.Page page,string TempFileDir) 根据页面对象和上传文件临时目录文件夹,获取一个文件上传上下文类实例
GetUploadContext 根据页面发送的上传会话编号,获取文件上传上下文
GetUploadContext(string GUID) 根据会话编号的 GUID获取文件上传的上下文
Release 在页面逻辑处理完成后,释放上传上下文,并删除临时文件
uploadStatus :文件上传状态枚举类
1、 将组件 dll复制到应用系统 bin目录,然后再 VS.NET中引用本组件,
2、 在 web.config增加 ASP.NET处理模块
<httpModules>
<add name="BigFileUploadModuleHandle " type="HelpSoft.BigFileUploadModuleHandle,BigFileUploadHandle" />
</httpModules>
3、 在要上传文件的 asp.net页面的页面装入事件中或初始化事件中,初始化文件上传环境,注册文件上传存放的临时文件夹,如下面代码:
private void Page_Load(object sender, System.EventArgs e)
{
UploadContext context = UploadContextFactory.InitUploadContext(this, @"c:" myupload"");
}
提示: A、文件存放目录要求设置相应的访问权限,目录必须存在
B、初始化上传环境后,页面须在一天内发送文件。否则上传环境失效。
C、初始化上传环境时,组件将在页面内注册隐藏域,记录上传会话唯一标志。隐藏域命名为 UploadID, 页面不能有同名控件。
D、需要上传文件表单编码方式必须为 multipart/form-data,(对于文件上传控件作为服务器端控件运行时,系统会自动加入此编码方式,否则需用户手工添加)
E、要使本上传组件生效,必须在页面访问时初始化上传环境,和设置表单编码方式,反之,如不希望本组件对特定页面生效,只可取消前两条件之一。
4、 在文件上传提交事件中,直接从上传文件环境工厂类中获取文件上传环境,进一步获取上传的文件信息。类似代码如下:
UploadContext context = UploadContextFactory.GetUploadContext();
string FileName = context.GetFileNameByControl(resFile.UniqueID);
string filePath=@"c:"workdir";
if (context.SaveFile(resFile.UniqueID,filePath+FileName))
{
UploadContextFactory.Release();
}
提示: A、根据控件名获取对应的上传文件信息时,对服务器控件需要传入控件唯一标志,对非服务器控件,需传入控件名称。
B、对文件另存时的目录访问权限,是否存在、可用空间大小,由页面调用者检查处理。如此类型错误发生,系统将引发对应异常。
C、如没找到控件对应文件(可能是客户端没有指定文件或传的控件标志有误)、临时目录中对应文件也不存在(已成功调用过此方法,文件已被移走),本方法将返回假,操作成功后,方法返回真。
D、 UploadContextFactory.Release()将从系统缓存中清除上传环境对象,将不能用 GetUploadContext方法获取上传环境,但已取得的上传环境仍然可用。
5、 读取文件上传进度时,需另建一个 ASPX页面,读取进度主要是根据上传文件的会话唯一标志,从系统缓存中(通过 GetUploadContext(string GUID)方法))获取上传文件环境,访问此环境类,即可以获取文件上传开始时间、信息总长度、当前已传送信息长度、速度、估计剩余时间等信息。系统再使用定时刷新的办法,不断获得上传进度信息,即可形成上传进度提示。
进度显示可以使用 XML无回刷新方式,也可以使用页面定时刷新方式。
6、 要在传输过程中,中断传输入过程,只需用会话唯一标志,获取上传环境,将一上传环境的 abort属性设为真,系统将主动与客户端断开连接,中断上传过程,同时页面提交的其它数据也将取消。
现在把NET2.0的源代码提供给大家,希望对大家有用。点击这里下载 源码。
由于我错误的将我的测试代码放到网上了,让许多朋友调试过通不过。今天我完整的将系统测试了一遍,实现了在默认的.NET配置下,上传28M文件。并且修改了以前的一个BUG:最后一次读取数据时,时间过长。源码已更新,欢迎各位指正。
在测试时,请在C: 盘根目录下建一个名为myupload 目录用于存放临时文件,同时在IIS中建一个名称myupload 的虚拟目录,存放最终文件。需要保证Asp.net对这两个目录有对应的操作权限。同时,你也可以在Default.aspx.cs中,修改这两个目录。
我是在英文XP+Asp.NET 2.0下测试通过。
欢迎大家指正。