http://www.cnblogs.com/ASPNET2008/archive/2013/10/10/3360427.html
最近公司一同事咨询了一个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 ,但它没有完成对文件的保存功能,我这里加了断点续传的逻辑。
组件对于浏览器的要求
看到大家对于js分割文件上传产生了不解,其实这个jQuery组件对浏览器是有要求的,利用了一些全新的api,上面文章中有提到浏览器的要求,对于只能使用低版本浏览器的项目来讲可能目前还有些问题,但异步上传是没问题的。
Here it is running in Chrome: (Note: In Chrome, Firefox, and Safari you'll get a multiple file selection, in IE 9 and under, you wont. IE 10 you will)
效果图:
可支持同时上传多个文件
上传成功后的效果
主要是控制进展条的显示以及组件的部分参数,比如文件块大小等等。
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. 完成断点续传后台逻辑
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
serializer =
new
System.Web.Script.Serialization.JavaScriptSerializer();
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来实现断点续传,即解决了性能问题也解决了用户体验。
注:
这只是我个人测试的代码,如觉的不妥,可自行修改。