当你使用IFormFile接口来上传文件的时候,一定要注意,IFormFile会将一个Http请求中的所有文件都读取到服务器内存后,才会触发ASP.NET Core MVC的Controller中的Action方法。这种情况下,如果上传一些小文件是没问题的,但是如果上传大文件,势必会造成服务器内存大量被占用甚至溢出,所以IFormFile接口只适合小文件上传。

一个文件上传页面的Html代码一般如下所示:

  1. <form method="post" enctype="multipart/form-data" action="/Upload">
  2. <div>
  3. <p>Upload one or more files using this form:</p>
  4. <input type="file" name="files" />
  5. </div>
  6. <div>
  7. <input type="submit" value="Upload" />
  8. </div>
  9. </form>

为了支持文件上传,form标签上一定要记得声明属性enctype="multipart/form-data",否者你会发现ASP.NET Core MVC的Controller中死活都读不到任何文件。Input type="file"标签在html 5中支持上传多个文件,加上属性multiple即可。

使用IFormFile接口上传文件非常简单,将其声明为Contoller中Action的集合参数即可:

  1. [HttpPost]
  2. public async Task<IActionResult> Post(List<IFormFile> files)
  3. {
  4. long size = files.Sum(f => f.Length);
  5.  
  6. foreach (var formFile in files)
  7. {
  8. var filePath = @"D:\UploadingFiles\" + formFile.FileName;
  9.  
  10. if (formFile.Length > )
  11. {
  12. using (var stream = new FileStream(filePath, FileMode.Create))
  13. {
  14. await formFile.CopyToAsync(stream);
  15. }
  16. }
  17. }
  18.  
  19. return Ok(new { count = files.Count, size });
  20. }

注意上面Action方法Post的参数名files,必须要和上传页面中的Input type="file"标签的name属性值一样。

用文件流 (大文件上传)

 在介绍这个方法之前我们先来看看一个包含上传文件的Http请求是什么样子的:

  1. Content-Type=multipart/form-data; boundary=---------------------------
  2. -----------------------------
  3. Content-Disposition: form-data; name="SOMENAME"
  4.  
  5. Formulaire de Quota
  6. -----------------------------
  7. Content-Disposition: form-data; name="OTHERNAME"
  8.  
  9. SOMEDATA
  10. -----------------------------
  11. Content-Disposition: form-data; name="files"; filename="Misc 001.jpg"
  12.  
  13. SDFESDSDSDJXCK+DSDSDSSDSFDFDF423232DASDSDSDFDSFJHSIHFSDUIASUI+/==
  14. -----------------------------
  15. Content-Disposition: form-data; name="files"; filename="Misc 002.jpg"
  16.  
  17. ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
  18. -----------------------------
  19. Content-Disposition: form-data; name="files"; filename="Misc 003.jpg"
  20.  
  21. TGUHGSDSDJXCK+DSDSDSSDSFDFDSAOJDIOASSAADDASDASDASSADASDSDSDSDFDSFJHSIHFSDUIASUI+/==
  22. -------------------------------

这就是一个multipart/form-data格式的Http请求,我们可以看到第一行信息是Http header,这里我们只列出了Content-Type这一行Http header信息,这和我们在html页面中form标签上的enctype属性值一致,第一行中接着有一个boundary=---------------------------99614912995,boundary=后面的值是随机生成的,这个其实是在声明Http请求中表单数据的分隔符是什么,其代表的是在Http请求中每读到一行 ---------------------------99614912995,表示一个section数据,一个section有可能是一个表单的键值数据,也有可能是一个上传文件的文件数据。每个section的第一行是section header,其中Content-Disposition属性都为form-data,表示这个section来自form标签提交的表单数据,如果section header拥有filename或filenamestar属性,那么表示这个section是一个上传文件的文件数据,否者这个section是一个表单的键值数据,section header之后的行就是这个section真正的数据行。例如我们上面的例子中,前两个section就是表单键值对,后面三个section是三个上传的图片文件。

那么接下来,我们来看看怎么用文件流来上传大文件,避免一次性将所有上传的文件都加载到服务器内存中。用文件流来上传比较麻烦的地方在于你无法使用ASP.NET Core MVC的模型绑定器来将上传文件反序列化为C#对象(如同前面介绍的IFormFile接口那样)。首先我们需要定义类MultipartRequestHelper,用于识别Http请求中的各个section类型(是表单键值对section,还是上传文件section)

  1. using System;
  2. using System.IO;
  3. using Microsoft.Net.Http.Headers;
  4.  
  5. namespace AspNetCore.MultipartRequest
  6. {
  7. public static class MultipartRequestHelper
  8. {
  9. // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
  10. // The spec says 70 characters is a reasonable limit.
  11. public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
  12. {
  13. //var boundary = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary);// .NET Core <2.0
  14. var boundary = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; //.NET Core 2.0
  15. if (string.IsNullOrWhiteSpace(boundary))
  16. {
  17. throw new InvalidDataException("Missing content-type boundary.");
  18. }
  19.  
  20. //注意这里的boundary.Length指的是boundary=---------------------------99614912995中等号后面---------------------------99614912995字符串的长度,也就是section分隔符的长度,上面也说了这个长度一般不会超过70个字符是比较合理的
  21. if (boundary.Length > lengthLimit)
  22. {
  23. throw new InvalidDataException(
  24. $"Multipart boundary length limit {lengthLimit} exceeded.");
  25. }
  26.  
  27. return boundary;
  28. }
  29.  
  30. public static bool IsMultipartContentType(string contentType)
  31. {
  32. return !string.IsNullOrEmpty(contentType)
  33. && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= ;
  34. }
  35.  
  36. //如果section是表单键值对section,那么本方法返回true
  37. public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
  38. {
  39. // Content-Disposition: form-data; name="key";
  40. return contentDisposition != null
  41. && contentDisposition.DispositionType.Equals("form-data")
  42. && string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
  43. && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); // For .NET Core <2.0 remove ".Value"
  44. }
  45.  
  46. //如果section是上传文件section,那么本方法返回true
  47. public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
  48. {
  49. // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
  50. return contentDisposition != null
  51. && contentDisposition.DispositionType.Equals("form-data")
  52. && (!string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
  53. || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value)); // For .NET Core <2.0 remove ".Value"
  54. }
  55.  
  56. // 如果一个section的Header是: Content-Disposition: form-data; name="files"; filename="Misc 002.jpg"
  57. // 那么本方法返回: files
  58. public static string GetFileContentInputName(ContentDispositionHeaderValue contentDisposition)
  59. {
  60. return contentDisposition.Name.Value;
  61. }
  62.  
  63. // 如果一个section的Header是: Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
  64. // 那么本方法返回: Misc 002.jpg
  65. public static string GetFileName(ContentDispositionHeaderValue contentDisposition)
  66. {
  67. return contentDisposition.FileName.Value;
  68. }
  69. }
  70. }

然后我们需要定义一个扩展类叫FileStreamingHelper,其中的StreamFiles扩展方法用于读取上传文件的文件流数据,并且将数据写入到服务器的硬盘上,其接受一个参数targetDirectory,用于声明将上传文件存储到服务器的哪个文件夹下。

  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.AspNetCore.Http.Features;
  3. using Microsoft.AspNetCore.Mvc.ModelBinding;
  4. using Microsoft.AspNetCore.WebUtilities;
  5. using Microsoft.Net.Http.Headers;
  6. using System;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Text;
  10. using System.Threading.Tasks;
  11.  
  12. namespace AspNetCore.MultipartRequest
  13. {
  14. public static class FileStreamingHelper
  15. {
  16. private static readonly FormOptions _defaultFormOptions = new FormOptions();
  17.  
  18. public static async Task<FormValueProvider> StreamFiles(this HttpRequest request, string targetDirectory)
  19. {
  20. if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
  21. {
  22. throw new Exception($"Expected a multipart request, but got {request.ContentType}");
  23. }
  24.  
  25. // Used to accumulate all the form url encoded key value pairs in the
  26. // request.
  27. var formAccumulator = new KeyValueAccumulator();
  28.  
  29. var boundary = MultipartRequestHelper.GetBoundary(
  30. MediaTypeHeaderValue.Parse(request.ContentType),
  31. _defaultFormOptions.MultipartBoundaryLengthLimit);
  32. var reader = new MultipartReader(boundary, request.Body);
  33.  
  34. var section = await reader.ReadNextSectionAsync();//用于读取Http请求中的第一个section数据
  35. while (section != null)
  36. {
  37. ContentDispositionHeaderValue contentDisposition;
  38. var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
  39.  
  40. if (hasContentDispositionHeader)
  41. {
  42. /*
  43. 用于处理上传文件类型的的section
  44. -----------------------------99614912995
  45. Content - Disposition: form - data; name = "files"; filename = "Misc 002.jpg"
  46.  
  47. ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
  48. -----------------------------99614912995
  49. */
  50. if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
  51. {
  52. if (!Directory.Exists(targetDirectory))
  53. {
  54. Directory.CreateDirectory(targetDirectory);
  55. }
  56.  
  57. var fileName = MultipartRequestHelper.GetFileName(contentDisposition);
  58.  
  59. var loadBufferBytes = ;//这个是每一次从Http请求的section中读出文件数据的大小,单位是Byte即字节,这里设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写入下面targetFileStream的文件流中,可以根据服务器的内存大小调整这个值。这样就避免了一次加载所有上传文件的数据到服务器内存中,导致服务器崩溃。
  60.  
  61. using (var targetFileStream = System.IO.File.Create(targetDirectory + "\\" + fileName))
  62. {
  63. //section.Body是System.IO.Stream类型,表示的是Http请求中一个section的数据流,从该数据流中可以读出每一个section的全部数据,所以我们下面也可以不用section.Body.CopyToAsync方法,而是在一个循环中用section.Body.Read方法自己读出数据,再将数据写入到targetFileStream
  64. await section.Body.CopyToAsync(targetFileStream, loadBufferBytes);
  65. }
  66.  
  67. }
  68. /*
  69. 用于处理表单键值数据的section
  70. -----------------------------99614912995
  71. Content - Disposition: form - data; name = "SOMENAME"
  72.  
  73. Formulaire de Quota
  74. -----------------------------99614912995
  75. */
  76. else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
  77. {
  78. // Content-Disposition: form-data; name="key"
  79. //
  80. // value
  81.  
  82. // Do not limit the key name length here because the
  83. // multipart headers length limit is already in effect.
  84. var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
  85. var encoding = GetEncoding(section);
  86. using (var streamReader = new StreamReader(
  87. section.Body,
  88. encoding,
  89. detectEncodingFromByteOrderMarks: true,
  90. bufferSize: ,
  91. leaveOpen: true))
  92. {
  93. // The value length limit is enforced by MultipartBodyLengthLimit
  94. var value = await streamReader.ReadToEndAsync();
  95. if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
  96. {
  97. value = String.Empty;
  98. }
  99. formAccumulator.Append(key.Value, value); // For .NET Core <2.0 remove ".Value" from key
  100.  
  101. if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
  102. {
  103. throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
  104. }
  105. }
  106. }
  107. }
  108.  
  109. // Drains any remaining section body that has not been consumed and
  110. // reads the headers for the next section.
  111. section = await reader.ReadNextSectionAsync();//用于读取Http请求中的下一个section数据
  112. }
  113.  
  114. // Bind form data to a model
  115. var formValueProvider = new FormValueProvider(
  116. BindingSource.Form,
  117. new FormCollection(formAccumulator.GetResults()),
  118. CultureInfo.CurrentCulture);
  119.  
  120. return formValueProvider;
  121. }
  122.  
  123. private static Encoding GetEncoding(MultipartSection section)
  124. {
  125. MediaTypeHeaderValue mediaType;
  126. var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
  127. // UTF-7 is insecure and should not be honored. UTF-8 will succeed in
  128. // most cases.
  129. if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
  130. {
  131. return Encoding.UTF8;
  132. }
  133. return mediaType.Encoding;
  134. }
  135. }
  136. }

现在我们还需要创建一个ASP.NET Core MVC的自定义拦截器DisableFormValueModelBindingAttribute,该拦截器实现接口IResourceFilter,用来禁用ASP.NET Core MVC的模型绑定器,这样当一个Http请求到达服务器后,ASP.NET Core MVC就不会在将请求的所有上传文件数据都加载到服务器内存后,才执行Contoller的Action方法,而是当Http请求到达服务器时,就立刻执行Contoller的Action方法。

  1. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  2. public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
  3. {
  4. public void OnResourceExecuting(ResourceExecutingContext context)
  5. {
  6. var formValueProviderFactory = context.ValueProviderFactories
  7. .OfType<FormValueProviderFactory>()
  8. .FirstOrDefault();
  9. if (formValueProviderFactory != null)
  10. {
  11. context.ValueProviderFactories.Remove(formValueProviderFactory);
  12. }
  13.  
  14. var jqueryFormValueProviderFactory = context.ValueProviderFactories
  15. .OfType<JQueryFormValueProviderFactory>()
  16. .FirstOrDefault();
  17. if (jqueryFormValueProviderFactory != null)
  18. {
  19. context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
  20. }
  21. }
  22.  
  23. public void OnResourceExecuted(ResourceExecutedContext context)
  24. {
  25. }
  26. }

最后我们在Contoller中定义一个叫Index的Action方法,并注册我们定义的DisableFormValueModelBindingAttribute拦截器,来禁用Action的模型绑定。Index方法会调用我们前面定义的FileStreamingHelper类中的StreamFiles方法,其参数为用来存储上传文件的文件夹路径。StreamFiles方法会返回一个FormValueProvider,用来存储Http请求中的表单键值数据,之后我们会将其绑定到MVC的视图模型viewModel上,然后将viewModel传回给客户端浏览器,来告述客户端浏览器文件上传成功。

  1. [HttpPost]
  2. [DisableFormValueModelBinding]
  3. public async Task<IActionResult> Index()
  4. {
  5. FormValueProvider formModel;
  6. formModel = await Request.StreamFiles(@"D:\UploadingFiles");
  7.  
  8. var viewModel = new MyViewModel();
  9.  
  10. var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
  11. valueProvider: formModel);
  12.  
  13. if (!bindingSuccessful)
  14. {
  15. if (!ModelState.IsValid)
  16. {
  17. return BadRequest(ModelState);
  18. }
  19. }
  20.  
  21. return Ok(viewModel);
  22. }

视图模型viewModel的定义如下:

  1. public class MyViewModel
  2. {
  3. public string Username { get; set; }
  4. }

最后我们用于上传文件的html页面和前面几乎一样:

  1. <form method="post" enctype="multipart/form-data" action="/Home/Index">
  2. <div>
  3. <p>Upload one or more files using this form:</p>
  4. <input type="file" name="files" multiple />
  5. </div>
  6. <div>
  7. <p>Your Username</p>
  8. <input type="text" name="username" />
  9. </div>
  10. <div>
  11. <input type="submit" value="Upload" />
  12. </div>
  13. </form>

到这里 上传大文件时提示404

在创建的项目里面是没有 “web.config” 文件的。

上传大文件时需要配置下文件的大小,需要在 “config” 文件里配置。创建一个或复制一个 “web.config”,代码:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <system.webServer>
  4. <security>
  5. <requestFiltering>
  6. <!--单位:字节。 -->
  7. <requestLimits maxAllowedContentLength="1073741824" />
  8. <!-- 1 GB -->
  9. </requestFiltering>
  10. </security>
  11. </system.webServer>
  12. </configuration>

然后在 Startup.cs 文件中代码如下:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. //设置接收文件长度的最大值。
  4. services.Configure<FormOptions>(x =>
  5. {
  6. x.ValueLengthLimit = int.MaxValue;
  7. x.MultipartBodyLengthLimit = int.MaxValue;
  8. x.MultipartHeadersLengthLimit = int.MaxValue;
  9. });
  10.  
  11. services.AddMvc();
  12. }
  1.  

.NET Core 如何上传文件及处理大文件上传的更多相关文章

  1. java读取 500M 以上文件,java读取大文件

    java 读取txt,java读取大文件 设置缓存大小BUFFER_SIZE ,Config.tempdatafile是文件地址 来源博客http://yijianfengvip.blog.163.c ...

  2. ASP.NET Core MVC如何上传文件及处理大文件上传

    用文件模型绑定接口:IFormFile (小文件上传) 当你使用IFormFile接口来上传文件的时候,一定要注意,IFormFile会将一个Http请求中的所有文件都读取到服务器内存后,才会触发AS ...

  3. CXF:通过WebService上传文件,包括大文件的处理

    参考网上文章,用CXF发布上传文件接口,并上传大文件的测试. 框架:spring3.1+cxf2.7.6 1.定义文件类实体 import javax.activation.DataHandler; ...

  4. 判断大文件是否上传成功(一个大文件上传到ftp,判断是否上传完成)

    大文件上传ftp,不知道有没有上传完成,如果没有上传完成另一个程序去下载这个文件,导致下载不完整. 判断一个文件是否上传完成的方法: /** * 间隔一段时间去计算文件的长度来判断文件是否写入完成 * ...

  5. formdata方式上传文件,支持大文件分割上传

    1.upload.html <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/html"> <h ...

  6. 单文件WebUploader做大文件的分块和断点续传

    前言: WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件.在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流 ...

  7. php文件上传参考配置与大文件上传

      PHP用超级全局变量数组$_FILES来记录文件上传相关信息的,在php文件上传之前,可通过调节php.ini中相关配置指令,来控制上传相关细节. 1.file_uploads=on/off   ...

  8. PHP上传文件参考配置大文件上传

    PHP用超级全局变量数组$_FILES来记录文件上传相关信息的. 1.file_uploads=on/off 是否允许通过http方式上传文件 2.max_execution_time=30 允许脚本 ...

  9. android下大文件分割上传

    由于android自身的原因,对大文件(如影视频文件)的操作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题. 文件分割后分多次请求服务. //文件分割上传 ...

随机推荐

  1. BOM设计的一些问题及解决方案探讨----合版BOM

    BOM是ERP的核心资料,也是比较难的一块,不仅涉及的内容多,要求准确性高,时效性也要求高.但传统的ERP在处理BOM时有不少问题,因此也有些软件公司引入了各种BOM类型,像"标准BOM&q ...

  2. ERP项目实施记录10

    好久没有更新,因为进度一直拖着.已经实施了20个月了,很多东西没有开发出来.原因多方面的,虽然在此打算吐槽一下开发公司,但其实很大部分责任还是在我们自己. 不多说了,看图:

  3. python全栈开发 * 33 知识点汇总 * 180718

    33 udp协议编码 显示客户端名字,输出带颜色的内容 udp协议的时间同步机制 #一.udp 协议编码 一个服务器,多个客户端#服务器:# import socket# sk=socket.sock ...

  4. python-----函数参数类型

    #函数参数类型:1 位置参数 2 默认参数 3 关键字参数 4可变参数 包裹位置参数*args 包裹关键字参数 **kargs#参数位置顺序:先位置参数,默认参数,包裹位置,包裹关键字(定义和调用都应 ...

  5. centos7忘记root密码重置

    1.重启服务器,选择内存按“e”编辑 2.找到下入内容 3.将上图中标记的ro改为rw init=/sysroot/bin/sh 4.按Ctrl+x进入单用户模式 5.执行命令chroot /sysr ...

  6. 【Mac】-NO.100.Mac.1.java.1.001-【Mac Install multiple JDK】-

    Style:Mac Series:Java Since:2018-09-10 End:2018-09-10 Total Hours:1 Degree Of Diffculty:5 Degree Of ...

  7. spring重要知识点总结

    一.面向切面编程 配置applicationContext.xml文件 <beans xmlns="http://www.springframework.org/schema/bean ...

  8. async await 的使用。 其实就是和then一样,只不过改变了链式写法

    这样写显得更加舒服.

  9. Tensorflow之调试(Debug)及打印变量

    参考资料:https://wookayin.github.io/tensorflow-talk-debugging 几种常用方法: 1.通过Session.run()获取变量的值 2.利用Tensor ...

  10. Java代码走查具体考察点

    代码走查具体考察点 一.参数检验 公共方法都要做参数的校验,参数校验不通过,需要明确抛出异常或对应响应码. 在接口中也明确使用验证注解修饰参数和返回值,作为一种协议要求调用方按注解约束传参,返回值验证 ...