Mvc下異步斷點續傳大文件


最近公司一同事咨詢了一個MVC項目下上傳大文件時遇到的問題,問題描述如下:

MVC項目中,當上傳比較大的文件時,速度非常慢,小文件基本沒有影響。

原因分析:

如果是用傳統的form表單去提交的話,會將整個文件一次性的加載到內存中然后再做保存,這個過程是相當慢的,特別是大文件,且上傳文件容易受到各種因素的影響而導致上傳失敗,比如臨時的網絡故障等。

如何解決?

最直接的概念就是異步以及斷點續傳。

為什么要異步

  1. 如果一個表單提交的元素中有文件上傳的需求,如最終因為文件上傳失敗而影響整個表單數據的提交,這個體驗性是非常差的。
  2. 如果上傳文件時間特別長,容易使應用程序長時間失去響應,給用戶一個錯覺,最好的方法是先讓用戶選擇文件,此時點擊上傳,后台進行文件的異步上傳,此時用戶還可以繼續去填寫其它的表單元素,等用戶填寫完其它表單元素,文件有可能已經上傳完成了,再提交表單,就只處理數據而不再上傳文件了。增強了用戶體驗性。

為什么要斷點續傳

在處理大文件時,無法忍受因為一時的網絡原因導致上傳失敗,從而重新再上傳的煩惱。好的方法是將一個大文件分成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. 引入jQuery上傳文件組件
  2. 初始化上傳組件

          主要是控制進展條的顯示以及組件的部分參數,比如文件塊大小等等。

  $(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上傳組件對於上傳進展條的一個監控  。   
        [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屬性來獲知此次處理的文件片斷,后續就是基本的文件操作了,沒什么可說的。

        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來實現斷點續傳,即解決了性能問題也解決了用戶體驗。

注:

        這只是我個人測試的代碼,如覺的不妥,可自行修改。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM