NetCore3.0 文件上传与大文件上传的限制

NetCore文件上传两种方式

  NetCore官方给出的两种文件上传方式分别为“缓冲”、“流式”。我简单的说说两种的区别,web

  1.缓冲:经过模型绑定先把整个文件保存到内存,而后咱们经过IFormFile获得stream,优势是效率高,缺点对内存要求大。文件不宜过大。api

  2.流式处理:直接读取请求体装载后的Section 对应的stream 直接操做strem便可。无需把整个请求体读入内存,缓存

如下为官方微软说法

缓冲

  整个文件读入 IFormFile,它是文件的 C# 表示形式,用于处理或保存文件。 文件上传所用的资源(磁盘、内存)取决于并发文件上传的数量和大小。 若是应用尝试缓冲过多上传,站点就会在内存或磁盘空间不足时崩溃。 若是文件上传的大小或频率会消耗应用资源,请使用流式传输。服务器

流式处理   

  从多部分请求收到文件,而后应用直接处理或保存它。 流式传输没法显著提升性能。 流式传输可下降上传文件时对内存或磁盘空间的需求。并发

文件大小限制

  提及大小限制,咱们得从两方面入手,1应用服务器Kestrel 2.应用程序(咱们的netcore程序),dom

1.应用服务器Kestre设置

  应用服务器Kestrel对咱们的限制主要是对整个请求体大小的限制经过以下配置能够进行设置(Program -> CreateHostBuilder),超出设置范围会报 BadHttpRequestException: Request body too large 异常信息async

public static IHostBuilder CreateHostBuilder(string[] args) =>
           Host.CreateDefaultBuilder(args)
               .ConfigureWebHostDefaults(webBuilder =>
               {
                   webBuilder.ConfigureKestrel((context, options) =>
                   {
                       //设置应用服务器Kestrel请求体最大为50MB
                       options.Limits.MaxRequestBodySize = 52428800;
                   });
                   webBuilder.UseStartup<Startup>();
});

2.应用程序设置

  应用程序设置 (Startup->  ConfigureServices) 超出设置范围会报InvalidDataException 异常信息ide

services.Configure<FormOptions>(options =>
 {
             options.MultipartBodyLengthLimit = long.MaxValue;
 });

经过设置即重置文件上传的大小限制。源码分析

源码分析

  这里我主要说一下 MultipartBodyLengthLimit  这个参数他主要限制咱们使用“缓冲”形式上传文件时每一个的长度。为何说是缓冲形式中,是由于咱们缓冲形式在读取上传文件用的帮助类为 MultipartReaderStream 类下的 Read 方法,此方法在每读取一次后会更新下读入的总byte数量,当超过此数量时会抛出  throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");  主要体如今 UpdatePosition 方法对 _observedLength  的判断性能

如下为 MultipartReaderStream 类两个方法的源代码,为方便阅读,我已精简掉部分代码

Read

public override int Read(byte[] buffer, int offset, int count)
 {
          
          var bufferedData = _innerStream.BufferedData;
      int read;
      read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
          return UpdatePosition(read);
}

UpdatePosition

private int UpdatePosition(int read)
        {
            _position += read;
            if (_observedLength < _position)
            {
                _observedLength = _position;
                if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
                {
                    throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");
                }
            }
            return read;
}

经过代码咱们能够看到 当你作了 MultipartBodyLengthLimit 的限制后,在每次读取后会累计读取的总量,当读取总量超出

 MultipartBodyLengthLimit  设定值会抛出 InvalidDataException 异常,

最终个人文件上传Controller以下

  须要注意的是咱们建立 MultipartReader 时并未设置 BodyLengthLimit  (这参数会传给 MultipartReaderStream.LengthLimit )也就是咱们最终的限制,这里我未设置值也就无限制,能够经过 UpdatePosition 方法体现出来

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System.IO;
using System.Threading.Tasks;
 
namespace BigFilesUpload.Controllers
{
    [Route("api/[controller]")]
    public class FileController : Controller
    {
        private readonly string _targetFilePath = "C:\\files\\TempDir";
 
        /// <summary>
        /// 流式文件上传
        /// </summary>
        /// <returns></returns>
        [HttpPost("UploadingStream")]
        public async Task<IActionResult> UploadingStream()
        {
 
            //获取boundary
            var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
            //获得reader
            var reader = new MultipartReader(boundary, HttpContext.Request.Body);
            //{ BodyLengthLimit = 2000 };//
            var section = await reader.ReadNextSectionAsync();
 
            //读取section
            while (section != null)
            {
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
                if (hasContentDispositionHeader)
                {
                    var trustedFileNameForFileStorage = Path.GetRandomFileName();
                    await WriteFileAsync(section.Body, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
                }
                section = await reader.ReadNextSectionAsync();
            }
            return Created(nameof(FileController), null);
        }
 
        /// <summary>
        /// 缓存式文件上传
        /// </summary>
        /// <param name=""></param>
        /// <returns></returns>
        [HttpPost("UploadingFormFile")]
        public async Task<IActionResult> UploadingFormFile(IFormFile file)
        {
            using (var stream = file.OpenReadStream())
            {
                var trustedFileNameForFileStorage = Path.GetRandomFileName();
                await WriteFileAsync(stream, Path.Combine(_targetFilePath, trustedFileNameForFileStorage));
            }
            return Created(nameof(FileController), null);
        }
 
 
        /// <summary>
        /// 写文件导到磁盘
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="path">文件保存路径</param>
        /// <returns></returns>
        public static async Task<int> WriteFileAsync(System.IO.Stream stream, string path)
        {
            const int FILE_WRITE_SIZE = 84975;//写出缓冲区大小
            int writeCount = 0;
            using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write, FILE_WRITE_SIZE, true))
            {
                byte[] byteArr = new byte[FILE_WRITE_SIZE];
                int readCount = 0;
                while ((readCount = await stream.ReadAsync(byteArr, 0, byteArr.Length)) > 0)
                {
                    await fileStream.WriteAsync(byteArr, 0, readCount);
                    writeCount += readCount;
                }
            }
            return writeCount;
        }
 
    }
}

 

 总结:

若是你部署 在iis上或者Nginx 等其余应用服务器 也是须要注意的事情,由于他们自己也有对请求体的限制,还有值得注意的就是咱们在建立文件流对象时 缓冲区的大小尽可能不要超过netcore大对象的限制。这样在并发高的时候很容易触发二代GC的回收.

相关文章
相关标签/搜索