2023年7月14日发(作者:)
.NETCore如何上传⽂件及处理⼤⽂件上传当你使⽤IFormFile接⼝来上传⽂件的时候,⼀定要注意,IFormFile会将⼀个Http请求中的所有⽂件都读取到服务器内存后,才会触发 Core MVC的Controller中的Action⽅法。这种情况下,如果上传⼀些⼩⽂件是没问题的,但是如果上传⼤⽂件,势必会造成服务器内存⼤量被占⽤甚⾄溢出,所以IFormFile接⼝只适合⼩⽂件上传。⼀个⽂件上传页⾯的Html代码⼀般如下所⽰:
为了⽀持⽂件上传,form标签上⼀定要记得声明属性enctype="multipart/form-data",否者你会发现 Core MVC的Controller中死活都读不到任何⽂件。Inputtype="file"标签在html 5中⽀持上传多个⽂件,加上属性multiple即可。使⽤IFormFile接⼝上传⽂件⾮常简单,将其声明为Contoller中Action的集合参数即可:[HttpPost]public async Taskreturn Ok(new { count = , size });}注意上⾯Action⽅法Post的参数名files,必须要和上传页⾯中的Input type="file"标签的name属性值⼀样。
⽤⽂件流 (⼤⽂件上传) 在介绍这个⽅法之前我们先来看看⼀个包含上传⽂件的Http请求是什么样⼦的:Content-Type=multipart/form-data; boundary=---------------------------99614912995-----------------------------99614912995Content-Disposition: form-data; name="SOMENAME"Formulaire de Quota-----------------------------99614912995Content-Disposition: form-data; name="OTHERNAME"SOMEDATA-----------------------------99614912995Content-Disposition: form-data; name="files"; filename="Misc "SDFESDSDSDJXCK+DSDSDSSDSFDFDF423232DASDSDSDFDSFJHSIHFSDUIASUI+/==-----------------------------99614912995Content-Disposition: form-data; name="files"; filename="Misc "ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==-----------------------------99614912995Content-Disposition: form-data; name="files"; filename="Misc "TGUHGSDSDJXCK+DSDSDSSDSFDFDSAOJDIOASSAADDASDASDASSADASDSDSDSDFDSFJHSIHFSDUIASUI+/==-----------------------------99614912995--这就是⼀个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是三个上传的图⽚⽂件。
那么接下来,我们来看看怎么⽤⽂件流来上传⼤⽂件,避免⼀次性将所有上传的⽂件都加载到服务器内存中。⽤⽂件流来上传⽐较⿇烦的地⽅在于你⽆法使⽤ CoreMVC的模型绑定器来将上传⽂件反序列化为C#对象(如同前⾯介绍的IFormFile接⼝那样)。⾸先我们需要定义类MultipartRequestHelper,⽤于识别Http请求中的各个section类型(是表单键值对section,还是上传⽂件section)using System;using ;using s;namespace artRequest{ public static class MultipartRequestHelper { // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq" // The spec says 70 characters is a reasonable limit. public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit) { //var boundary = Quotes(ry);// .NET Core <2.0 var boundary = Quotes(ry).Value; //.NET Core 2.0 if (OrWhiteSpace(boundary)) { throw new InvalidDataException("Missing content-type boundary."); } //注意这⾥的指的是boundary=---------------------------99614912995中等号后⾯---------------------------99614912995字符串的长度,也就是section分隔符的长度,上⾯也说了这个长度⼀般不会超过70个字符是⽐较合理的 if ( > lengthLimit) { throw new InvalidDataException( $"Multipart boundary length limit {lengthLimit} exceeded."); } return boundary; } public static bool IsMultipartContentType(string contentType) { return !OrEmpty(contentType) && f("multipart/", lIgnoreCase) >= 0; } //如果section是表单键值对section,那么本⽅法返回true public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition) { // Content-Disposition: form-data; name="key"; return contentDisposition != null && ("form-data") && OrEmpty() // For .NET Core <2.0 remove ".Value" && OrEmpty(); // For .NET Core <2.0 remove ".Value" } //如果section是上传⽂件section,那么本⽅法返回true public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition) { // Content-Disposition: form-data; name="myfile1"; filename="Misc " return contentDisposition != null && ("form-data") && (!OrEmpty() // For .NET Core <2.0 remove ".Value" || !OrEmpty()); // For .NET Core <2.0 remove ".Value" } // 如果⼀个section的Header是: Content-Disposition: form-data; name="files"; filename="Misc " // 那么本⽅法返回: files public static string GetFileContentInputName(ContentDispositionHeaderValue contentDisposition) { return ; } // 如果⼀个section的Header是: Content-Disposition: form-data; name="myfile1"; filename="Misc " // 那么本⽅法返回: Misc public static string GetFileName(ContentDispositionHeaderValue contentDisposition) { return ; } }}然后我们需要定义⼀个扩展类叫FileStreamingHelper,其中的StreamFiles扩展⽅法⽤于读取上传⽂件的⽂件流数据,并且将数据写⼊到服务器的硬盘上,其接受⼀个参数targetDirectory,⽤于声明将上传⽂件存储到服务器的哪个⽂件夹下。using ;using es;using inding;using lities;using s;using System;using ization;using ;using ;using ;namespace artRequest{ public static class FileStreamingHelper { private static readonly FormOptions _defaultFormOptions = new FormOptions(); public static async Task
// request. var formAccumulator = new KeyValueAccumulator(); var boundary = ndary( (tType), _artBoundaryLengthLimit); var reader = new MultipartReader(boundary, ); var section = await xtSectionAsync();//⽤于读取Http请求中的第⼀个section数据 while (section != null) { ContentDispositionHeaderValue contentDisposition; var hasContentDispositionHeader = se(tDisposition, out contentDisposition); if (hasContentDispositionHeader) { /* ⽤于处理上传⽂件类型的的section -----------------------------99614912995 Content - Disposition: form - data; name = "files"; filename = "Misc " ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/== -----------------------------99614912995 */ if (eContentDisposition(contentDisposition)) { if (!(targetDirectory)) { Directory(targetDirectory); } var fileName = eName(contentDisposition); var loadBufferBytes = 1024;//这个是每⼀次从Http请求的section中读出⽂件数据的⼤⼩,单位是Byte即字节,这⾥设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写⼊下⾯targ using (var targetFileStream = (targetDirectory + "" + fileName)) { //是类型,表⽰的是Http请求中⼀个section的数据流,从该数据流中可以读出每⼀个section的全部数据,所以我们下⾯也可以不⽤Async⽅法,⽽是在⼀个循环中⽤ await Async(targetFileStream, loadBufferBytes); } } /* ⽤于处理表单键值数据的section -----------------------------99614912995 Content - Disposition: form - data; name = "SOMENAME" Formulaire de Quota -----------------------------99614912995 */ else if (mDataContentDisposition(contentDisposition)) { // Content-Disposition: form-data; name="key" // // value // Do not limit the key name length here because the
// multipart headers length limit is already in effect. var key = Quotes(); var encoding = GetEncoding(section); using (var streamReader = new StreamReader( , encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) { // The value length limit is enforced by MultipartBodyLengthLimit var value = await EndAsync(); if ((value, "undefined", lIgnoreCase)) { value = ; } (, value); // For .NET Core <2.0 remove ".Value" from key if (ount > _ountLimit) { throw new InvalidDataException($"Form key count limit {_ountLimit} exceeded."); } } } } // Drains any remaining section body that has not been consumed and // reads the headers for the next section. section = await xtSectionAsync();//⽤于读取Http请求中的下⼀个section数据 } // Bind form data to a model var formValueProvider = new FormValueProvider( , new FormCollection(ults()), tCulture); return formValueProvider; } private static Encoding GetEncoding(MultipartSection section) { MediaTypeHeaderValue mediaType; var hasMediaTypeHeader = se(tType, out mediaType); // UTF-7 is insecure and should not be honored. UTF-8 will succeed in
// most cases. if (!hasMediaTypeHeader || (ng)) { return 8; } return ng; } }}现在我们还需要创建⼀个 Core MVC的⾃定义拦截器DisableFormValueModelBindingAttribute,该拦截器实现接⼝IResourceFilter,⽤来禁⽤ Core MVC的模型绑定器,这样当⼀个Http请求到达服务器后, Core MVC就不会在将请求的所有上传⽂件数据都加载到服务器内存后,才执⾏Contoller的Action⽅法,⽽是当Http请求到达服务器时,就⽴刻执⾏Contoller的Action⽅法。[AttributeUsage( | )]public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter{ public void OnResourceExecuting(ResourceExecutingContext context) { var formValueProviderFactory = roviderFactories .OfType
var jqueryFormValueProviderFactory = roviderFactories .OfType
public void OnResourceExecuted(ResourceExecutedContext context) { }}最后我们在Contoller中定义⼀个叫Index的Action⽅法,并注册我们定义的DisableFormValueModelBindingAttribute拦截器,来禁⽤Action的模型绑定。Index⽅法会调⽤我们前⾯定义的FileStreamingHelper类中的StreamFiles⽅法,其参数为⽤来存储上传⽂件的⽂件夹路径。StreamFiles⽅法会返回⼀个FormValueProvider,⽤来存储Http请求中的表单键值数据,之后我们会将其绑定到MVC的视图模型viewModel上,然后将viewModel传回给客户端浏览器,来告述客户端浏览器⽂件上传成功。[HttpPost][DisableFormValueModelBinding]public async Task
发布者:admin,转转请注明出处:http://www.yc00.com/news/1689308044a228416.html
评论列表(0条)