首页 后端开发 C#.Net教程 .NetCore实现上传多文件的示例详解

.NetCore实现上传多文件的示例详解

Apr 19, 2017 pm 04:36 PM

本章和大家分享的是.NetCore的MVC框架上传文件的示例,主要讲的内容有:form方式提交上传,ajax上传,ajax提交+上传进度效果,Task并行处理+ajax提交+上传进度,对大家非常有帮助,感兴趣的朋友跟随小编一起学习吧

本章和大家分享的是.NetCore的MVC框架上传文件的示例,主要讲的内容有:form方式提交上传,ajax上传,ajax提交+上传进度效果,Task并行处理+ajax提交+上传进度,相信当你读完文章内容后能后好的收获,如果可以不妨点个赞;由于昨天电脑没电了,快要写完的内容没有保存,今天早上提前来公司从头开始重新,断电这情况的确让人很头痛啊,不过为了社区的分享环境,这也是值得的,不多说了来进入今天的正篇环节吧;

form方式上传一组图片

先来看看咋们html的代码,这里先简单说下要上传文件必须要设置form元素里面的 enctype="multipart/form-data" 属性和post方式,如果你想要多选上传文件的话,需要把文件type='file'元素设置她的属性multiple='multiple',因此就有了如下内容:


<form class="form-horizontal" action="/Home/FileUp" method="post" enctype="multipart/form-data">
  <input type="file" name="MyPhoto" class="form-control" multiple />
  <br />
  <button class="btn btn-default">form上传</button>
  <br />
  <span style="color:red">@ViewData["MsgBox"]</span>
  </form>
登录后复制

由于采用form提交,这个测试用例只接用了button元素默认的type=submit来提交表单,对应的后台Action中代码如下:


/// <summary>
 /// form提交上传
 /// </summary>
 /// <param name="user"></param>
 /// <returns></returns>
 [HttpPost]
 public async Task<IActionResult> FileUp(MoUser user)
 {
  if (user.MyPhoto == null || user.MyPhoto.Count <= 0) { MsgBox("请上传图片。"); return View(); }
  //var file = Request.Form.Files;
  foreach (var file in user.MyPhoto)
  {
  var fileName = file.FileName;
  var contentType = file.ContentType;
  var len = file.Length;
  var fileType = new string[] { "image/jpeg", "image/png" };
  if (!fileType.Any(b => b.Contains(contentType))) { MsgBox($"只能上传{string.Join(",", fileType)}格式的图片。"); return View(); }
  if (len > 1024 * 1024 * 4) { MsgBox("上传图片大小只能在4M以下。"); return View(); }
  var path = Path.Combine(@"D:\F\学习\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName);
  using (var stream = System.IO.File.Create(path))
  {
   await file.CopyToAsync(stream);
  }
  }
  MsgBox($"上传成功");
  return View();
 }
登录后复制

从前端到后端的Action不得不说这种form表单提交的方式挺简单的,需要注意的是Action这里用的实体模型方式来对应上传的文件信息,这里自定义了MoUser类,通过属性 public List<IFormFile> MyPhoto { get; set; } 来匹配html表单中文件type=&#39;file&#39;的name属性名称name="MyPhoto"


public class MoUser
 {
 public int UserId { get; set; } = 1;
 public string UserName { get; set; } = "神牛步行3";
 public List<IFormFile> MyPhoto { get; set; }
 }
登录后复制

这样就能通过实体模型的方式把上传的文件信息存储在自定义MoUser类中的MyPhoto属性中了;

ajax上传一组图片

这里需要在上面例子中的html处修改一些东西,不再使用form提交,指定了普通button按钮来触发ajax的提交,完整html代码如:


<form class="form-horizontal" id="form01" method="post" enctype="multipart/form-data">
  <input type="file" name="MyPhoto01" class="form-control" multiple />
  <br />
  <button type="button" id="btnAjax" class="btn btn-default">ajax上传</button>
  <br />
  <span style="color:red" id="span01"></span>
  </form>
登录后复制

有了布局,再来看看具体的js实现代码,这里我采用jquery的ajax提交的方法来操作,也用到了html5新增的FormData来存储表单的数据:


$("#btnAjax").on("click", function () {
  var msg = $("#span01");
  var form = document.getElementById("form01");
  //console.log(form);
  var data = new FormData(form);
  $.ajax({
  type: "POST",
  url: "/home/AjaxFileUp",
  data: data,
  contentType: false,
  processData: false,
  success: function (data) {
   if (data) {
   msg.html(data.msg);
   }
  },
  error: function () {
   msg.html("上传文件异常,请稍后重试!");
  }
  });
 });
登录后复制

至于后台Action的方法和示例一的相差不大,关键点在于这里我直接使用 Request.Form.Files 方式来获取上传的所有文件,不再使用实体模型的方式了,这样测试用例更多样化吧:


/// <summary>
 /// ajax无上传进度效果上传
 /// </summary>
 /// <returns></returns>
 [HttpPost]
 public async Task<JsonResult> AjaxFileUp()
 {
  var data = new MoData { Msg = "上传失败" };
  try
  {
  var files = Request.Form.Files.Where(b => b.Name == "MyPhoto01");
  //非空限制
  if (files == null || files.Count() <= 0) { data.Msg = "请选择上传的文件。"; return Json(data); }
  //格式限制
  var allowType = new string[] { "image/jpeg", "image/png" };
  if (files.Any(b => !allowType.Contains(b.ContentType)))
  {
   data.Msg = $"只能上传{string.Join(",", allowType)}格式的文件。";
   return Json(data);
  }
  //大小限制
  if (files.Sum(b => b.Length) >= 1024 * 1024 * 4)
  {
   data.Msg = "上传文件的总大小只能在4M以下。"; return Json(data);
  }
  //写入服务器磁盘
  foreach (var file in files)
  {
   var fileName = file.FileName;
   var path = Path.Combine(@"D:\F\学习\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName);
   using (var stream = System.IO.File.Create(path))
   {
   await file.CopyToAsync(stream);
   }
  }
  data.Msg = "上传成功";
  data.Status = 2;
  }
  catch (Exception ex)
  {
  data.Msg = ex.Message;
  }
  return Json(data);
 }
登录后复制

如果你有耐心读到这里,那么后面的内容个人感觉对你开发会有好的帮助,不负你期待;

ajax提交+上传进度+一组图片上传

同样我们先来看对应的html代码,其实和示例2几乎一样,只是把名称变动了下:


<form class="form-horizontal" id="form02" method="post" enctype="multipart/form-data">
  <input type="file" name="MyPhoto02" class="form-control" multiple />
  <br />
  <button type="button" id="btnAjax02" class="btn btn-default">ajax上传进度效果上传</button>
  <br />
  <span style="color:red" id="span02"></span>
  </form>
登录后复制

要加一个进度效果,需要用到js的定时器,定时获取上传文件的上传进度数据信息,因此这里通过js的setInterval方法来定时请求一个进度数据接口,注意用完之后需要清除这个定时器,不然一直再不断请求您接口:


 $("#btnAjax02").on("click", function () {
  var interBar;
  var msg = $("#span02");
  msg.html("上传中,请稍后...");
  var form = document.getElementById("form02");
  //console.log(form);
  var data = new FormData(form);
  $.ajax({
   type: "POST",
   url: "/home/AjaxFileUp02",
   data: data,
   contentType: false,
   processData: false,
   success: function (data) {
   if (data) {
    msg.html(data.msg);
    //清除进度查询
    if (interBar) { clearInterval(interBar); }
   }
   },
   error: function () {
   msg.html("上传文件异常,请稍后重试!");
   if (interBar) { clearInterval(interBar); }
   }
  });
  //获取进度
  interBar = setInterval(function () {
   $.post("/home/ProgresBar02", function (data) {
   if (data) {
    var isClearVal = true;
    var strArr = [];
    $.each(data, function (i, item) {
    strArr.push(&#39;文件:&#39; + item.fileName + ",当前上传:" + item.percentBar + &#39;<br/>&#39;);
    if (item.status != 2) { isClearVal = false; }
    });
    msg.html(strArr.join(&#39;&#39;));
    if (isClearVal) {
    if (interBar) { clearInterval(interBar); }
    }
   }
   });
  }, 200);
  });
登录后复制

既然上面说到单独的进度数据接口,那么我们除了上传Action外,也需要进度的Action,而这进度Action得到的上传文件数据信息必须和上传的Action一直,因此就需要用到缓存等存储数据的方式,这里我用的是MemoryCache的方式,对已netcore来说仅仅只需要在起始文件(如:Startup.cs)中添加组件服务:


 public void ConfigureServices(IServiceCollection services)
  {
  // Add framework services.
  services.AddMvc();
 
  //添加cache支持
  services.AddDistributedMemoryCache();
 }
登录后复制

然后通过构造函数注入到对应的接口Controller中去:


  readonly IMemoryCache _cache;
 public HomeController(IOptions<MoOptions> options, ILogger<HomeController> logger, IMemoryCache cache)
 {  this._options = options.Value;
  _logger = logger;
  _cache = cache; 
 }
登录后复制

到此我们就能利用cache来存储我们上传进度信息了,来看下处理上传的Action:


private string cacheKey = "UserId_UpFile";
 private string cacheKey03 = "UserId_UpFile03";
 /// <summary>
 /// ajax上传进度效果上传
 /// </summary>
 /// <returns></returns>
 [HttpPost]
 public async Task<JsonResult> AjaxFileUp02()
 {
  var data = new MoData { Msg = "上传失败" };
  try
  {
  var files = Request.Form.Files.Where(b => b.Name == "MyPhoto02");
  //非空限制
  if (files == null || files.Count() <= 0) { data.Msg = "请选择上传的文件。"; return Json(data); }
  //格式限制
  var allowType = new string[] { "image/jpeg", "image/png" };
  if (files.Any(b => !allowType.Contains(b.ContentType)))
  {
   data.Msg = $"只能上传{string.Join(",", allowType)}格式的文件。";
   return Json(data);
  }
  //大小限制
  if (files.Sum(b => b.Length) >= 1024 * 1024 * 4)
  {
   data.Msg = "上传文件的总大小只能在4M以下。"; return Json(data);
  }
  //初始化上传多个文件的Bar,存储到缓存中,方便获取上传进度
  var listBar = new List<MoBar>();
  files.ToList().ForEach(b =>
  {
   listBar.Add(new MoBar
   {
   FileName = b.FileName,
   Status = 1,
   CurrBar = 0,
   TotalBar = b.Length
   });
  });
  _cache.Set<List<MoBar>>(cacheKey, listBar);
  //写入服务器磁盘
  foreach (var file in files)
  {
   //总大小
   var totalSize = file.Length;
   //初始化每次读取大小
   var readSize = 1024L;
   var bt = new byte[totalSize > readSize ? readSize : totalSize];
   //当前已经读取的大小
   var currentSize = 0L;
   var fileName = file.FileName;
   var path = Path.Combine(@"D:\F\学习\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName);
   using (var stream = System.IO.File.Create(path))
   {
   //await file.CopyToAsync(stream);
   //进度条处理流程
   using (var inputStream = file.OpenReadStream())
   {
    //读取上传文件流
    while (await inputStream.ReadAsync(bt, 0, bt.Length) > 0)
    {
    //当前读取的长度
    currentSize += bt.Length;
    //写入上传流到服务器文件中
    await stream.WriteAsync(bt, 0, bt.Length);
    //获取每次读取的大小
    readSize = currentSize + readSize <= totalSize ?
     readSize :
     totalSize - currentSize;
    //重新设置
    bt = new byte[readSize];
    //设置当前上传的文件进度,并重新缓存到进度缓存中
    var bars = _cache.Get<List<MoBar>>(cacheKey);
    var currBar = bars.Where(b => b.FileName == fileName).SingleOrDefault();
    currBar.CurrBar = currentSize;
    currBar.Status = currentSize >= totalSize ? 2 : 1;
    _cache.Set<List<MoBar>>(cacheKey, bars);
    System.Threading.Thread.Sleep(1000 * 1);
    }
   }
   }
  }
  data.Msg = "上传完成";
  data.Status = 2;
  }
  catch (Exception ex)
  {
  data.Msg = ex.Message;
  }
  return Json(data);
 }
登录后复制

代码一下子就变多了,其实按照逻辑来说增加了存储进度的Cache,和逐一读取上传文件流的逻辑而已,具体大家可以仔细看下代码,都有备注说明;再来就是咋们的进度信息Action接口:


[HttpPost]
  public JsonResult ProgresBar02()
  {
   var bars = new List<MoBar>();
   try
   {
    bars = _cache.Get<List<MoBar>>(cacheKey);
   }
   catch (Exception ex)
   {
   }
   return Json(bars);
  }
登录后复制

进度接口只需要获取cache中的进度信息就行了,注:这里是测试用例,具体使用场景请各位自行增加其他逻辑代码;下面就来看下效果截图:

Task并行处理+ajax提交+上传进度+一组图片上传

这一小节,将会使用Task来处理上传的文件,通过上一小节截图能够看出,如果你上传多个文件,那么都是按照次序一个一个读取文件流来生成上传文件到服务器,这里改良一下利用Task的特点,就能实现同时读取不同文件流了,先来看下html代码和js代码:


<form class="form-horizontal" id="form03" method="post" enctype="multipart/form-data">
    <input type="file" name="MyPhoto03" class="form-control" multiple />
    <br />
    <button type="button" id="btnAjax03" class="btn btn-default">task任务处理ajax上传进度效果上传</button>
    <br />
    <span style="color:red" id="span03"></span>
   </form>
登录后复制

由于和示例3的js代码无差别这里我直接贴出代码:


$("#btnAjax03").on("click", function () {
   var interBar;
   var msg = $("#span03");
   msg.html("上传中,请稍后...");
   var form = document.getElementById("form03");
   //console.log(form);
   var data = new FormData(form);
   $.ajax({
    type: "POST",
    url: "/home/AjaxFileUp03",
    data: data,
    contentType: false,
    processData: false,
    success: function (data) {
     if (data) {
      msg.html(data.msg);
      //清除进度查询
      if (interBar) { clearInterval(interBar); }
     }
    },
    error: function () {
     msg.html("上传文件异常,请稍后重试!");
     if (interBar) { clearInterval(interBar); }
    }
   });
   //获取进度
   interBar = setInterval(function () {
    $.post("/home/ProgresBar03", function (data) {
     if (data) {
      var isClearVal = true;
      var strArr = [];
      $.each(data, function (i, item) {
       strArr.push(&#39;文件:&#39; + item.fileName + ",当前上传:" + item.percentBar + &#39;<br/>&#39;);
       if (item.status != 2) { isClearVal = false; }
      });
      msg.html(strArr.join(&#39;&#39;));
      if (isClearVal) {
       if (interBar) { clearInterval(interBar); }
      }
     }
    });
   }, 200);
  });
登录后复制

关键点在后台,通过task数组来存储每个上传文件的处理任务 Task[] tasks = new Task[len]; ,然后使用 Task.WaitAll(tasks); 等待所有上传任务的完成,这里特别注意了这里必须等待,不然会丢失上传文件流(多次测试结果):


/// <summary>
  /// ajax上传进度效果上传
  /// </summary>
  /// <returns></returns>
  [HttpPost]
  public JsonResult AjaxFileUp03()
  {
   var data = new MoData { Msg = "上传失败" };
   try
   {
    var files = Request.Form.Files.Where(b => b.Name == "MyPhoto03");
    //非空限制
    if (files == null || files.Count() <= 0) { data.Msg = "请选择上传的文件。"; return Json(data); }
    //格式限制
    var allowType = new string[] { "image/jpeg", "image/png" };
    if (files.Any(b => !allowType.Contains(b.ContentType)))
    {
     data.Msg = $"只能上传{string.Join(",", allowType)}格式的文件。";
     return Json(data);
    }
    //大小限制
    if (files.Sum(b => b.Length) >= 1024 * 1024 * 4)
    {
     data.Msg = "上传文件的总大小只能在4M以下。"; return Json(data);
    }
    //初始化上传多个文件的Bar,存储到缓存中,方便获取上传进度
    var listBar = new List<MoBar>();
    files.ToList().ForEach(b =>
    {
     listBar.Add(new MoBar
     {
      FileName = b.FileName,
      Status = 1,
      CurrBar = 0,
      TotalBar = b.Length
     });
    });
    _cache.Set<List<MoBar>>(cacheKey03, listBar);
    var len = files.Count();
    Task[] tasks = new Task[len];
    //写入服务器磁盘
    for (int i = 0; i < len; i++)
    {
     var file = files.Skip(i).Take(1).SingleOrDefault();
     tasks[i] = Task.Factory.StartNew((p) =>
     {
      var item = p as IFormFile;
      //总大小
      var totalSize = item.Length;
      //初始化每次读取大小
      var readSize = 1024L;
      var bt = new byte[totalSize > readSize ? readSize : totalSize];
      //当前已经读取的大小
      var currentSize = 0L;
      var fileName = item.FileName;
      var path = Path.Combine(@"D:\F\学习\vs2017\netcore\netcore01\WebApp01\wwwroot\myfile", fileName);
      using (var stream = System.IO.File.Create(path))
      {
       //进度条处理流程
       using (var inputStream = item.OpenReadStream())
       {
        //读取上传文件流
        while (inputStream.Read(bt, 0, bt.Length) > 0)
        {
         //当前读取的长度
         currentSize += bt.Length;
         //写入上传流到服务器文件中
         stream.Write(bt, 0, bt.Length);
         //获取每次读取的大小
         readSize = currentSize + readSize <= totalSize ?
           readSize :
           totalSize - currentSize;
         //重新设置
         bt = new byte[readSize];
         //设置当前上传的文件进度,并重新缓存到进度缓存中
         var bars = _cache.Get<List<MoBar>>(cacheKey03);
         var currBar = bars.Where(b => b.FileName == fileName).SingleOrDefault();
         currBar.CurrBar = currentSize;
         currBar.Status = currentSize >= totalSize ? 2 : 1;
         _cache.Set<List<MoBar>>(cacheKey03, bars);
         System.Threading.Thread.Sleep(1000 * 1);
        }
       }
      }
     }, file);
    }
    //任务等待 ,这里必须等待,不然会丢失上传文件流
    Task.WaitAll(tasks);
    data.Msg = "上传完成";
    data.Status = 2;
   }
   catch (Exception ex)
   {
    data.Msg = ex.Message;
   }
   return Json(data);
  }
登录后复制

至于获取上传进度的Action也仅仅只是读取缓存数据而已:


[HttpPost]
  public JsonResult ProgresBar03()
  {
   var bars = new List<MoBar>();
   try
   {
    bars = _cache.Get<List<MoBar>>(cacheKey03);
   }
   catch (Exception ex)
   {
   }
   return Json(bars);
  }
登录后复制

这里再给出上传进度的实体类:


public class MoData
 {
  /// <summary>
  /// 0:失败 1:上传中 2:成功
  /// </summary>
  public int Status { get; set; }

  public string Msg { get; set; }
 }

 public class MoBar : MoData
 {
  /// <summary>
  /// 文件名字
  /// </summary>
  public string FileName { get; set; }

  /// <summary>
  /// 当前上传大小
  /// </summary>
  public long CurrBar { get; set; }

  /// <summary>
  /// 总大小
  /// </summary>
  public long TotalBar { get; set; }

  /// <summary>
  /// 进度百分比
  /// </summary>
  public string PercentBar
  {
   get
   {
    return $"{(this.CurrBar * 100 / this.TotalBar)}%";
   }
  }
 }
登录后复制

到此task任务处理上传文件的方式就完成了,咋们来看图看效果吧:

能够通过示例3和4的效果图对比出,没使用Task和使用的效果区别,这样效果您值得拥有,耐心读完本文内容的朋友,没让你失望吧,如果可以不妨点个"赞"或扫个码支持下作者,谢谢;内容最后附上具体测试用例代码:.NetCore上传多文件的几种示例

以上是.NetCore实现上传多文件的示例详解的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

C语言各种符号的使用方法 C语言各种符号的使用方法 Apr 03, 2025 pm 04:48 PM

C 语言中符号的使用方法涵盖算术、赋值、条件、逻辑、位运算符等。算术运算符用于基本数学运算,赋值运算符用于赋值和加减乘除赋值,条件运算符用于根据条件执行不同操作,逻辑运算符用于逻辑操作,位运算符用于位级操作,特殊常量用于表示空指针、文件结束标记和非数字值。

char在C语言字符串中的作用是什么 char在C语言字符串中的作用是什么 Apr 03, 2025 pm 03:15 PM

在 C 语言中,char 类型在字符串中用于:1. 存储单个字符;2. 使用数组表示字符串并以 null 终止符结束;3. 通过字符串操作函数进行操作;4. 从键盘读取或输出字符串。

c#多线程和异步的区别 c#多线程和异步的区别 Apr 03, 2025 pm 02:57 PM

多线程和异步的区别在于,多线程同时执行多个线程,而异步在不阻塞当前线程的情况下执行操作。多线程用于计算密集型任务,而异步用于用户交互操作。多线程的优势是提高计算性能,异步的优势是不阻塞 UI 线程。选择多线程还是异步取决于任务性质:计算密集型任务使用多线程,与外部资源交互且需要保持 UI 响应的任务使用异步。

char与wchar_t在C语言中的区别 char与wchar_t在C语言中的区别 Apr 03, 2025 pm 03:09 PM

在 C 语言中,char 和 wchar_t 的主要区别在于字符编码:char 使用 ASCII 或扩展 ASCII,wchar_t 使用 Unicode;char 占用 1-2 个字节,wchar_t 占用 2-4 个字节;char 适用于英语文本,wchar_t 适用于多语言文本;char 广泛支持,wchar_t 依赖于编译器和操作系统是否支持 Unicode;char 的字符范围受限,wchar_t 的字符范围更大,并使用专门的函数进行算术运算。

char在C语言中如何处理特殊字符 char在C语言中如何处理特殊字符 Apr 03, 2025 pm 03:18 PM

C语言中通过转义序列处理特殊字符,如:\n表示换行符。\t表示制表符。使用转义序列或字符常量表示特殊字符,如char c = '\n'。注意,反斜杠需要转义两次。不同平台和编译器可能有不同的转义序列,请查阅文档。

char在C语言中如何进行类型转换 char在C语言中如何进行类型转换 Apr 03, 2025 pm 03:21 PM

在 C 语言中,char 类型转换可以通过:强制类型转换:使用强制类型转换符将一种类型的数据直接转换为另一种类型。自动类型转换:当一种类型的数据可以容纳另一种类型的值时,编译器自动进行转换。

C语言 sum 的作用是什么? C语言 sum 的作用是什么? Apr 03, 2025 pm 02:21 PM

C语言中没有内置求和函数,需自行编写。可通过遍历数组并累加元素实现求和:循环版本:使用for循环和数组长度计算求和。指针版本:使用指针指向数组元素,通过自增指针遍历高效求和。动态分配数组版本:动态分配数组并自行管理内存,确保释放已分配内存以防止内存泄漏。

char数组在C语言中如何使用 char数组在C语言中如何使用 Apr 03, 2025 pm 03:24 PM

char 数组在 C 语言中存储字符序列,声明为 char array_name[size]。访问元素通过下标运算符,元素以空终止符 '\0' 结尾,用于表示字符串终点。C 语言提供多种字符串操作函数,如 strlen()、strcpy()、strcat() 和 strcmp()。

See all articles