翼度科技»论坛 编程开发 .net 查看内容

ASP.NET Core MVC 从入门到精通之文件上传

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
随着技术的发展,ASP.NET Core MVC也推出了好长时间,经过不断的版本更新迭代,已经越来越完善,本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容,适用于初学者,在校毕业生,或其他想从事ASP.NET Core MVC 系统开发的人员。 经过前几篇文章的讲解,初步了解ASP.NET Core MVC项目创建,启动运行,以及命名约定,创建控制器,视图,模型,接收参数,传递数据ViewData,ViewBag,路由,页面布局,wwwroot和客户端库,Razor语法,EnityFrameworkCore与数据库,HttpContext,Request,Response,Session,序列化等内容,今天继续讲解ASP.NET Core MVC 中文件上传等相关内容,仅供学习分享使用。

 
概述

 
在实际应用开发中,文件上传是非常常见的功能,文件上传主要分为单文件上传,多文件上传,文件与其他内容混合上传,大文件上传几种情况,本文会分别讲解。
 
IFormFile

 
在ASP.NET Core MVC项目中,IFormFile表示使用 HttpRequest 发送的文件,可以用于文件流的接收。将整个文件读入 IFormFile。 IFormFile 是用于处理或保存文件的文件的 C# 表示形式。
文件上传使用的磁盘和内存取决于并发文件上传的数量和大小。 如果应用尝试缓冲过多上传,站点就会在内存或磁盘空间不足时崩溃。 如果文件上传的大小或频率会消耗应用资源,请使用流式传输。
IFormFile的属性和方法如下:

 对于小文件的上传,一般采用IFormFile;大文件上传,采用流式上传,已实现可靠稳定传输。
IWebHostEnvironment 站点环境信息,用于获取站点根目录等内容。
 
单个文件上传

 
单文件上传功能主要分为两部分:文件上传视图和后台处理方法。
1. 文件上传视图

首先创建视图,用于单个文件上传。关于视图有两点说明,如下所示:

  • 文件上传通过form表单,采用post方式,加密类型为multipart/form-data
  • 文件上传采用input控件,类型为file。
视图代码如下所示:
  1. 1 <form method="post" enctype="multipart/form-data" action="/File/OneFileUpload">
  2. 2     <h1>单文件上传</h1>
  3. 3     
  4. 4         <span>文件:</span>
  5. 5         <input type="file" name="file" />
  6. 6     
  7. 7     <input type="submit" value="上传" />
  8. 8 </form>
复制代码
 
2. 后台处理方法

form提交后台处理方法OneFileUpload,关于处理方法有几点说明,如下所示:

  • 方法中的参数IFormFile  file用于接收客户端上传的文件,其他file和视图中上传控件的name一一对应。如果错误,则无法上传。
  • _webHostEnvironment 为控制器通过接口注入的IWebHostEnvironment类型的获取站点信息接口,主要用于获取站点根目录。
  • 调用IFormFile的CopyTo方法进行保存,此方法接收Stream类型的参数。
上传处理代码,如下所示:
  1. 1 /// <summary>
  2. 2 /// 单文件上传
  3. 3 /// </summary>
  4. 4 /// <returns></returns>
  5. 5 public IActionResult OneFileUpload(IFormFile file)
  6. 6 {
  7. 7     var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads", string.Format("{0}_{1}", DateTime.Now.Ticks, file.FileName));
  8. 8     using (FileStream fs = new FileStream(path, FileMode.Create))
  9. 9     {
  10. 10         file.CopyTo(fs);
  11. 11     }
  12. 12     return Ok("上传成功");
  13. 13 }
复制代码
 
多文件上传

 
多文件上传表示一次可以上传多个文件。。上传功能主要分为两部分:文件上传视图和后台处理方法。
 
1. 多文件上传视图

 
input控件在类型为file时表示文件上传,默认是单个文件上传,通过设置multiple属性,可实现多文件上传。视图代码如下所示:
  1. 1 <form method="post" enctype="multipart/form-data" action="/File/MoreFileUpload">
  2. 2     <h1>多文件上传</h1>
  3. 3     
  4. 4         <span>文件:</span>
  5. 5         <input type="file" name="files" multiple />
  6. 6     
  7. 7     <input type="submit" value="上传" />
  8. 8 </form>
复制代码
 
2. 多文件后台处理方法

 
多个文件上传,参数为IFormFile数组类型,可以接受上传文件列表,然后循环获取并进行保存即可。如下所示:
  1. 1 /// <summary>
  2. 2 /// 多文件上传
  3. 3 /// </summary>
  4. 4 /// <returns></returns>
  5. 5 public IActionResult MoreFileUpload(IFormFile[] files)
  6. 6 {
  7. 7     foreach (var file in files)
  8. 8     {
  9. 9         var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads", string.Format("{0}_{1}", DateTime.Now.Ticks, file.FileName));
  10. 10         using (FileStream fs = new FileStream(path, FileMode.Create))
  11. 11         {
  12. 12             file.CopyTo(fs);
  13. 13         }
  14. 14     }
  15. 15
  16. 16     return Ok("上传成功");
  17. 17 }
复制代码
 
文件文本混合上传

 
在实际应用中,文件上传只是一部分,还需要搭配其他的文本说明,如录入产品信息,并上传附件等。
 
1. 创建模型

 
在Product中,包含两个属性,一个字符串类型的Name,用于绑定名称,一个IFormFile类型的File,用于上传文件。如下所示:
  1. 1 namespace DemoCoreMVC.Models
  2. 2 {
  3. 3     public class Product
  4. 4     {
  5. 5         public string Name { get; set; }
  6. 6
  7. 7         public IFormFile File { get; set; }
  8. 8     }
  9. 9 }
复制代码
 
2. 视图绑定模型

 
在视图最顶部,为视图指定模型,如下所示:
  1. 1 @model DemoCoreMVC.Models.Product
复制代码
 
3. 混合文本文件上传视图

 
在form表单中,除了文件上传控件,还有一个文件框,用于输入名称。其中控件name和模型相对应。如下所示:
  1. 1 <form method="post" enctype="multipart/form-data" action="/File/FileWithContentUpload">
  2. 2     <h1>文件,文本混合上传</h1>
  3. 3     
  4. 4         <span>名称:</span>
  5. 5         <input type="text" name="Name"  />
  6. 6     
  7. 7     
  8. 8         <span>文件:</span>
  9. 9         <input type="file" name="File" />
  10. 10     
  11. 11     <input type="submit" value="上传" />
  12. 12 </form>
复制代码
 
4. 后台处理方法

 
文件文本混合上传,参数为模型Product,通过属性匹配接收参数,然后获取属性File对应的内存流进行保存即可。如下所示:
  1. 1 /// <summary>
  2. 2 /// 文件内容混合上传
  3. 3 /// </summary>
  4. 4 /// <returns></returns>
  5. 5 public IActionResult FileWithContentUpload(Product product)
  6. 6 {
  7. 7     var file = product.File;
  8. 8     var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads", string.Format("{0}_{1}", DateTime.Now.Ticks, file.FileName));
  9. 9     using (FileStream fs = new FileStream(path, FileMode.Create))
  10. 10     {
  11. 11         file.CopyTo(fs);
  12. 12     }
  13. 13     return Ok($"{product.Name} 上传成功");
  14. 14 }
复制代码
 
大文件上传

 
首先如何界定大文件/小文件,并没有统一的标准。根据官网相关参数说明:

  • 默认情况下, HttpRequest.Form 不会缓冲整个请求正文 (BufferBody) ,但会缓冲包含的任何多部分表单文件。
  • MultipartBodyLengthLimit 是缓冲表单文件的最大大小,默认值为 128MB。
  • MemoryBufferThreshold 指示在转换为磁盘上的缓冲区文件之前,内存中的文件缓冲量,默认为 64KB。
  • MemoryBufferThreshold 充当小型和大型文件之间的边界,这些文件根据应用资源和方案而引发或降低。
大文件上传采用流式传输,可降低上传文件时对内存或磁盘空间的需求。
 
1. 创建视图

 
大文件和小文件上传在视图上并无差别,只是后台处理方法不同,如下所示:
  1. 1 <form method="post" enctype="multipart/form-data" action="/File/BigFileUpload">
  2. 2     <h1>大文件上传</h1>
  3. 3     
  4. 4         <span>文件:</span>
  5. 5         <input type="file" name="file" />
  6. 6     
  7. 7     <input type="submit" value="上传" />
  8. 8 </form>
复制代码
 
2. 后台处理方法

 
首先创建大文件上传帮助类MultipartRequestHelper,如下所示:
  1. 1 using System;
  2. 2 using System.IO;
  3. 3 using Microsoft.Net.Http.Headers;
  4. 4 namespace DemoCoreMVC
  5. 5 {
  6. 6     public static class MultipartRequestHelper
  7. 7     {
  8. 8         // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
  9. 9         public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
  10. 10         {
  11. 11             var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
  12. 12
  13. 13             if (string.IsNullOrWhiteSpace(boundary))
  14. 14             {
  15. 15                 throw new InvalidDataException("Missing content-type boundary.");
  16. 16             }
  17. 17
  18. 18             if (boundary.Length > lengthLimit)
  19. 19             {
  20. 20                 throw new InvalidDataException(
  21. 21                     $"Multipart boundary length limit {lengthLimit} exceeded.");
  22. 22             }
  23. 23
  24. 24             return boundary;
  25. 25         }
  26. 26
  27. 27         public static bool IsMultipartContentType(string contentType)
  28. 28         {
  29. 29             return !string.IsNullOrEmpty(contentType)
  30. 30                    && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
  31. 31         }
  32. 32
  33. 33         public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
  34. 34         {
  35. 35             // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
  36. 36             return contentDisposition != null
  37. 37                 && contentDisposition.DispositionType.Equals("form-data")
  38. 38                 && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
  39. 39                     || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
  40. 40         }
  41. 41
  42. 42         // 如果一个section的Header是: Content-Disposition: form-data; name="myfile1"; filename="F:\Misc 002.jpg"
  43. 43         // 那么本方法返回: Misc 002.jpg
  44. 44         public static string GetFileName(ContentDispositionHeaderValue contentDisposition)
  45. 45         {
  46. 46             return Path.GetFileName(contentDisposition.FileName.Value);
  47. 47         }
  48. 48
  49. 49     }
  50. 50 }
复制代码
处理方法BigFileUploadAsync,关于Action说明,如下所示:
Action中要进行DisableRequestSizeLimit特性说明,否则会有大小限制【InvalidDataException: Multipart body length limit 16384 exceeded】。
在该操作中,使用 MultipartReader 读取窗体的内容,它会读取每个单独的 MultipartSection,从而根据需要处理文件或存储内容。 读取多部分节后,该操作会执行自己的模型绑定。
  1. 1 /// <summary>
  2. 2 /// 大文件上传
  3. 3 /// </summary>
  4. 4 /// <returns></returns>
  5. 5 [DisableRequestSizeLimit]
  6. 6 public async Task<IActionResult> BigFileUploadAsync()
  7. 7 {
  8. 8     var contentType = Request.ContentType;
  9. 9     if (!MultipartRequestHelper.IsMultipartContentType(contentType))
  10. 10     {
  11. 11         ModelState.AddModelError("File",
  12. 12             $"上传文件类型不对.");
  13. 13         return BadRequest(ModelState);
  14. 14     }
  15. 15     var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads");
  16. 16
  17. 17     var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
  18. 18
  19. 19     var reader = new MultipartReader(boundary, HttpContext.Request.Body);
  20. 20
  21. 21     var section = await reader.ReadNextSectionAsync();
  22. 22
  23. 23     while (section != null)
  24. 24     {
  25. 25         var hasContentDispositionHeader =
  26. 26             ContentDispositionHeaderValue.TryParse(
  27. 27                 section.ContentDisposition, out var contentDisposition);
  28. 28
  29. 29         if (hasContentDispositionHeader)
  30. 30         {
  31. 31             if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
  32. 32             {
  33. 33                 ModelState.AddModelError("File",
  34. 34                     $"The request couldn't be processed (Error 2).");
  35. 35
  36. 36                 return BadRequest(ModelState);
  37. 37             }
  38. 38             else
  39. 39             {
  40. 40                 var fileName = MultipartRequestHelper.GetFileName(contentDisposition);
  41. 41                 var loadBufferBytes = 1024;//这个是每一次从Http请求的section中读出文件数据的大小,单位是Byte即字节,这里设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写入下面targetFileStream的文件流中,可以根据服务器的内存大小调整这个值。这样就避免了一次加载所有上传文件的数据到服务器内存中,导致服务器崩溃。
  42. 42
  43. 43                 using (var targetFileStream = new FileStream(path + "\" + string.Format("{0}_{1}", DateTime.Now.Ticks, fileName), FileMode.Create, FileAccess.ReadWrite))
  44. 44                 {
  45. 45                     using (section.Body)
  46. 46                     {
  47. 47                         //section.Body是System.IO.Stream类型,表示的是Http请求中一个section的数据流,从该数据流中可以读出每一个section的全部数据,所以我们下面也可以不用section.Body.CopyToAsync方法,而是在一个循环中用section.Body.Read方法自己读出数据(如果section.Body.Read方法返回0,表示数据流已经到末尾,数据已经全部都读取完了),再将数据写入到targetFileStream
  48. 48                         await section.Body.CopyToAsync(targetFileStream, loadBufferBytes);
  49. 49                     }
  50. 50                 }
  51. 51             }
  52. 52         }
  53. 53         section = await reader.ReadNextSectionAsync();
  54. 54     }
  55. 55     return Ok("上传成功");
  56. 56 }
复制代码
注意:在文件上传功能中,上传后的文件一般都要进行重命名的,否则如何客户端上传相同名称的文件,则可能会被覆盖。在本例中,在原文件前面加上了时间戳,以减少重复的概率。
 
文件上传校验

 
在实际开发中,为了避免客户端上传不满足条件的文件,一般都会进行校验。

  • 文件扩展名验证:应在允许的扩展名列表中查找上传的文件的扩展名。
  • 文件签名验证:文件的签名由文件开头部分中的前几个字节确定。 可以使用这些字节指示扩展名是否与文件内容匹配。 示例应用检查一些常见文件类型的文件签名。
  • 文件名安全:切勿使用客户端提供的文件名来将文件保存到物理存储。
  • 文件大小验证:限制上传的文件的大小。
 
文件上传安全

 
为避免文件上传功能造成攻击可能性,常规安全措施如下:

  • 将文件上传到专用文件上传区域,最好是非系统驱动器。 使用专用位置便于对上传的文件实施安全限制。 禁用对文件上传位置的执行权限。
  • 请勿将上传的文件保存在与应用相同的目录树中。
  • 使用应用确定的安全的文件名。 请勿使用用户提供的文件名或上传的文件的不受信任的文件名。† 当显示不受信任的文件名时 HTML 会对它进行编码。 例如,记录文件名或在 UI 中显示(Razor 自动对输出进行 HTML 编码)。
  • 按照应用的设计规范,仅允许已批准的文件扩展名。
  • 验证是否对服务器执行客户端检查。† 客户端检查易于规避。
  • 检查已上传文件的大小。 设置一个大小上限以防止上传大型文件。
  • 文件不应该被具有相同名称的上传文件覆盖时,先在数据库或物理存储上检查文件名,然后再上传文件。
  • 先对上传的内容运行病毒/恶意软件扫描程序,然后再存储文件。
参考文章

本篇文章主要参考内容如下:
1. 官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0
 
以上就是ASP.NET Core MVC从入门到精通之文件上传的全部内容。

来源:https://www.cnblogs.com/hsiang/archive/2023/05/10/17386616.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具