[转载]Mvc下异步断点续传大文件 – min.jiang – 博客园.
最近公司一同事咨询了一个MVC项目下上传大文件时遇到的问题,问题描述如下:
MVC项目中,当上传比较大的文件时,速度非常慢,小文件基本没有影响。
原因分析:
如果是用传统的form表单去提交的话,会将整个文件一次性的加载到内存中然后再做保存,这个过程是相当慢的,特别是大文件,且上传文件容易受到各种因素的影响而导致上传失败,比如临时的网络故障等。
如何解决?
最直接的概念就是异步以及断点续传。
为什么要异步
- 如果一个表单提交的元素中有文件上传的需求,如最终因为文件上传失败而影响整个表单数据的提交,这个体验性是非常差的。
- 如果上传文件时间特别长,容易使应用程序长时间失去响应,给用户一个错觉,最好的方法是先让用户选择文件,此时点击上传,后台进行文件的异步上 传,此时用户还可以继续去填写其它的表单元素,等用户填写完其它表单元素,文件有可能已经上传完成了,再提交表单,就只处理数据而不再上传文件了。增强了 用户体验性。
为什么要断点续传
在处理大文件时,无法忍受因为一时的网络原因导致上传失败,从而重新再上传的烦恼。好的方法是将一个大文件分成N个小块来进行上传,即使第一次失败 了,之前上传的那部分由于得到了保留,再次点击上传时,以前已经传输成功的部分就不会再次被重新写入文件。注意,第二次上传时,文件还是从0开始传输到服 务器,而不能根据服务器上的文件选择性的传输片断,这部分不太好节省,有兴趣的可以研究下。
如何实现异步上传
这里可以利用JQuery的相应插件来完成,它的主要功能是将文件分割成N多个小块来批量上传,参考网址:https://github.com/blueimp/jQuery-File-Upload
如何实现断点续传
其实这个也非常简单,在Http头信息中有一个Conten-Range的属性,它会说明此次传递的文件内容的片断范围,我们只需要在后台解析这个 范围稍加处理就可以实现。之所这么简单,是因为有了上面的JQuery 上传文件的插件,它负责将一个大文件分成N多小块进行传输,这就有了请求头中的Content-Range。
这篇文章主要参考了http://weblogs.asp.net/bryansampica/archive/2013/01/15/AsyncMVCFileUpload.aspx ,但它没有完成对文件的保存功能,我这里加了断点续传的逻辑。
效果图:
可支持同时上传多个文件
上传成功后的效果
实现关键步骤:
- 引入JQuery上传文件组件
- 初始化上传组件
主要是控制进展条的显示以及组件的部分参数,比如文件块大小等等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
$( function () { $( '#fileupload' ).fileupload({ dataType: "json" , url: "/api/upload" , limitConcurrentUploads: 1, sequentialUploads: true , progressInterval: 100, maxChunkSize: 10000, add: function (e, data) { $( '#filelistholder' ).removeClass( 'hide' ); data.context = $( '<div />' ).text(data.files[0].name).appendTo( '#filelistholder' ); $( '</div><div class="progress"><div class="bar" style="width:0%"></div></div>' ).appendTo(data.context); $( '#btnUploadAll' ).click( function () { data.submit(); }); }, done: function (e, data) { data.context.text(data.files[0].name + '... Completed' ); $( '</div><div class="progress"><div class="bar" style="width:100%"></div></div>' ).appendTo(data.context); }, progressall: function (e, data) { var progress = parseInt(data.loaded / data.total * 100, 10); $( '#overallbar' ).css( 'width' , progress + '%' ); }, progress: function (e, data) { var progress = parseInt(data.loaded / data.total * 100, 10); data.context.find( '.bar' ).css( 'width' , progress + '%' ); } }); }); |
3. 完成断点续传后台逻辑
- 首先通过api来配合jQuery上传组件对于上传进展条的一个监控 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[HttpGet] [HttpPost] public HttpResponseMessage Upload() { // Get a reference to the file that our jQuery sent. Even with multiple files, they will all be their own request and be the 0 index HttpPostedFile file = HttpContext.Current.Request.Files[0]; // do something with the file in this space // {....} // end of file doing this .SaveAs(HttpContext.Current.Server.MapPath( "~/Images/" ) + file.FileName, file); // Now we need to wire up a response so that the calling script understands what happened HttpContext.Current.Response.ContentType = "text/plain" ; var result = new { name = file.FileName }; HttpContext.Current.Response.Write(serializer.Serialize(result)); HttpContext.Current.Response.StatusCode = 200; // For compatibility with IE's "done" event we need to return a result as well as setting the context.response return new HttpResponseMessage(HttpStatusCode.OK); } |
- 实现断点续传逻辑
这其中主要是通过解析Http请求头中的Content-Range属性来获知此次处理的文件片断,后续就是基本的文件操作了,没什么可说的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
private void SaveAs( string saveFilePath, HttpPostedFile file) { long lStartPos = 0; int startPosition = 0; int endPosition = 0; var contentRange=HttpContext.Current.Request.Headers[ "Content-Range" ]; //bytes 10000-19999/1157632 if (! string .IsNullOrEmpty(contentRange)) { contentRange = contentRange.Replace( "bytes" , "" ).Trim(); contentRange = contentRange.Substring(0, contentRange.IndexOf( "/" )); string [] ranges = contentRange.Split( '-' ); startPosition = int .Parse(ranges[0]); endPosition = int .Parse(ranges[1]); } System.IO.FileStream fs; if (System.IO.File.Exists(saveFilePath)) { fs = System.IO.File.OpenWrite(saveFilePath); lStartPos = fs.Length; } else { fs = new System.IO.FileStream(saveFilePath, System.IO.FileMode.Create); lStartPos = 0; } if (lStartPos > endPosition) { fs.Close(); return ; } else if (lStartPos < startPosition) { lStartPos = startPosition; } else if (lStartPos > startPosition && lStartPos < endPosition) { lStartPos = startPosition; } fs.Seek(lStartPos, System.IO.SeekOrigin.Current); byte [] nbytes = new byte [512]; int nReadSize = 0; nReadSize = file.InputStream.Read(nbytes, 0, 512); while (nReadSize > 0) { fs.Write(nbytes, 0, nReadSize); nReadSize = file.InputStream.Read(nbytes, 0, 512); } fs.Close(); } |
总结:
jQuery上传组件将大文件分割成小文件上传,正好解决了.net上传文件大小问题,只要将块大小配置好即可。利用Http头信息的Content-Range来实现断点续传,即解决了性能问题也解决了用户体验。
注:
这只是我个人测试的代码,如觉的不妥,可自行修改。