NetCore 3.0文件上传和大文件上传的限制详解
NetCore文件上传两种方式
NetCore官方给出的两种文件上传方式分别为“缓冲”、“流式”。我简单的说说两种的区别,
1.缓冲:通过模型绑定先把整个文件保存到内存,然后我们通过IFormFile得到stream,优点是效率高,缺点对内存要求大。文件不宜过大。
2.流式处理:直接读取请求体装载后的Section对应的stream直接操作strem即可。无需把整个请求体读入内存,
以下为官方微软说法
缓冲
整个文件读入IFormFile,它是文件的C#表示形式,用于处理或保存文件。文件上传所用的资源(磁盘、内存)取决于并发文件上传的数量和大小。如果应用尝试缓冲过多上传,站点就会在内存或磁盘空间不足时崩溃。如果文件上传的大小或频率会消耗应用资源,请使用流式传输。
流式处理
从多部分请求收到文件,然后应用直接处理或保存它。流式传输无法显著提高性能。流式传输可降低上传文件时对内存或磁盘空间的需求。
文件大小限制
说起大小限制,我们得从两方面入手,1应用服务器Kestrel2.应用程序(我们的netcore程序),
1.应用服务器Kestre设置
应用服务器Kestrel对我们的限制主要是对整个请求体大小的限制通过如下配置可以进行设置(Program->CreateHostBuilder),超出设置范围会报BadHttpRequestException:Requestbodytoolarge异常信息
publicstaticIHostBuilderCreateHostBuilder(string[]args)=> Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder=> { webBuilder.ConfigureKestrel((context,options)=> { //设置应用服务器Kestrel请求体最大为50MB options.Limits.MaxRequestBodySize=52428800; }); webBuilder.UseStartup(); });
2.应用程序设置
应用程序设置(Startup-> ConfigureServices)超出设置范围会报InvalidDataException异常信息
services.Configure(options=> { options.MultipartBodyLengthLimit=long.MaxValue; });
通过设置即重置文件上传的大小限制。
源码分析
这里我主要说一下MultipartBodyLengthLimit 这个参数他主要限制我们使用“缓冲”形式上传文件时每个的长度。为什么说是缓冲形式中,是因为我们缓冲形式在读取上传文件用的帮助类为MultipartReaderStream类下的Read方法,此方法在每读取一次后会更新下读入的总byte数量,当超过此数量时会抛出 thrownewInvalidDataException($"Multipartbodylengthlimit{LengthLimit.GetValueOrDefault()}exceeded."); 主要体现在UpdatePosition方法对_observedLength 的判断
以下为MultipartReaderStream类两个方法的源代码,为方便阅读,我已精简掉部分代码
Read
publicoverrideintRead(byte[]buffer,intoffset,intcount) { varbufferedData=_innerStream.BufferedData; intread; read=_innerStream.Read(buffer,offset,Math.Min(count,bufferedData.Count)); returnUpdatePosition(read); }
UpdatePosition
privateintUpdatePosition(intread) { _position+=read; if(_observedLength<_position) { _observedLength=_position; if(LengthLimit.HasValue&&_observedLength>LengthLimit.GetValueOrDefault()) { thrownewInvalidDataException($"Multipartbodylengthlimit{LengthLimit.GetValueOrDefault()}exceeded."); } } returnread; }
通过代码我们可以看到当你做了MultipartBodyLengthLimit的限制后,在每次读取后会累计读取的总量,当读取总量超出
MultipartBodyLengthLimit 设定值会抛出InvalidDataException异常,
最终我的文件上传Controller如下
需要注意的是我们创建MultipartReader时并未设置BodyLengthLimit (这参数会传给MultipartReaderStream.LengthLimit)也就是我们最终的限制,这里我未设置值也就无限制,可以通过UpdatePosition方法体现出来
usingMicrosoft.AspNetCore.Http; usingMicrosoft.AspNetCore.Mvc; usingMicrosoft.AspNetCore.WebUtilities; usingMicrosoft.Net.Http.Headers; usingSystem.IO; usingSystem.Threading.Tasks; namespaceBigFilesUpload.Controllers { [Route("api/[controller]")] publicclassFileController:Controller { privatereadonlystring_targetFilePath="C:\\files\\TempDir"; //////流式文件上传 /// ///[HttpPost("UploadingStream")] publicasyncTask UploadingStream() { //获取boundary varboundary=HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value; //得到reader varreader=newMultipartReader(boundary,HttpContext.Request.Body); //{BodyLengthLimit=2000};// varsection=awaitreader.ReadNextSectionAsync(); //读取section while(section!=null) { varhasContentDispositionHeader=ContentDispositionHeaderValue.TryParse(section.ContentDisposition,outvarcontentDisposition); if(hasContentDispositionHeader) { vartrustedFileNameForFileStorage=Path.GetRandomFileName(); awaitWriteFileAsync(section.Body,Path.Combine(_targetFilePath,trustedFileNameForFileStorage)); } section=awaitreader.ReadNextSectionAsync(); } returnCreated(nameof(FileController),null); } /// ///缓存式文件上传 /// ////// [HttpPost("UploadingFormFile")] publicasyncTask UploadingFormFile(IFormFilefile) { using(varstream=file.OpenReadStream()) { vartrustedFileNameForFileStorage=Path.GetRandomFileName(); awaitWriteFileAsync(stream,Path.Combine(_targetFilePath,trustedFileNameForFileStorage)); } returnCreated(nameof(FileController),null); } /// ///写文件导到磁盘 /// ///流 /// 文件保存路径 /// publicstaticasyncTask WriteFileAsync(System.IO.Streamstream,stringpath) { constintFILE_WRITE_SIZE=84975;//写出缓冲区大小 intwriteCount=0; using(FileStreamfileStream=newFileStream(path,FileMode.Create,FileAccess.Write,FileShare.Write,FILE_WRITE_SIZE,true)) { byte[]byteArr=newbyte[FILE_WRITE_SIZE]; intreadCount=0; while((readCount=awaitstream.ReadAsync(byteArr,0,byteArr.Length))>0) { awaitfileStream.WriteAsync(byteArr,0,readCount); writeCount+=readCount; } } returnwriteCount; } } }
总结:
如果你部署在iis上或者Nginx等其他应用服务器也是需要注意的事情,因为他们本身也有对请求体的限制,还有值得注意的就是我们在创建文件流对象时缓冲区的大小尽量不要超过netcore大对象的限制。这样在并发高的时候很容易触发二代GC的回收.
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。