zoukankan      html  css  js  c++  java
  • Mvc下异步断点续传大文件

    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 ,但它没有完成对文件的保存功能,我这里加了断点续传的逻辑。

    效果图:

              

    可支持同时上传多个文件

           

    上传成功后的效果

          

    实现关键步骤:

    1. 引入jQuery上传文件组件
    2. 初始化上传组件

              主要是控制进展条的显示以及组件的部分参数,比如文件块大小等等。

    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="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="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 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来实现断点续传,即解决了性能问题也解决了用户体验。

    注:

            这只是我个人测试的代码,如觉的不妥,可自行修改。

     

     

     

     

     

    分类: ASP.NET MVC

  • 相关阅读:
    GC(垃圾分代收集)
    排序算法总结
    Redis中的数据结构
    事务的隔离性(续篇)
    手写Spring mvc框架 (二)
    MySql日志与事务的隔离级别
    手写Spring mvc框架 (一)
    IO流
    随笔三(Ajax)
    关于博主noble_
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3360603.html
Copyright © 2011-2022 走看看