精华内容
下载资源
问答
  • C# 上传文件(防止内存溢出

    千次阅读 2015-05-27 21:09:53
    上传文件,之前使用WebClient的上传方法,UploadFile方法容易造成内存溢出,UploadData方法又一直没搞定,所以借鉴下网友的方法 文章主要内容源自...

    上传文件,之前使用WebClient的上传方法,UploadFile方法容易造成内存溢出,UploadData方法又一直没搞定,所以借鉴下网友的方法
    文章主要内容源自(http://blogs.msdn.com/b/johan/archive/2006/11/15/are-you-getting-outofmemoryexceptions-when-uploading-large-files.aspx),本文做些修改,使其可直接使用:

    public static string MyUploader(string strFileToUpload, string strUrl,Action<double,double> uploading)
    {
       string strFileFormName = "file";
       Uri oUri = new Uri(strUrl);
       string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");
    
       // The trailing boundary string
       byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + strBoundary + "--\r\n");
      //**注意此处的 strBoundary后的换行前加上--,不然会异常**
       // The post message header
       StringBuilder sb = new StringBuilder();
       sb.Append("--");
       sb.Append(strBoundary);
       sb.Append("\r\n");
       sb.Append("Content-Disposition: form-data; name=\"");
       sb.Append(strFileFormName);
       sb.Append("\"; filename=\"");
       sb.Append(Path.GetFileName(strFileToUpload));
       sb.Append("\"");
       sb.Append("\r\n");
       sb.Append("Content-Type: ");
       sb.Append("application/octet-stream");
       sb.Append("\r\n");
       sb.Append("\r\n");
       string strPostHeader = sb.ToString();
       byte[] postHeaderBytes = Encoding.UTF8.GetBytes(strPostHeader);
    
       // The WebRequest
       HttpWebRequest oWebrequest = (HttpWebRequest)WebRequest.Create(oUri);
       oWebrequest.ContentType = "multipart/form-data; boundary=" + strBoundary;
       oWebrequest.Method = "POST";
    
       // This is important, otherwise the whole file will be read to memory anyway...
       oWebrequest.AllowWriteStreamBuffering = false;
    
       // Get a FileStream and set the final properties of the WebRequest
       FileStream oFileStream = new FileStream(strFileToUpload, FileMode.Open, FileAccess.Read);
       long length = postHeaderBytes.Length + oFileStream.Length + boundaryBytes.Length;
       oWebrequest.ContentLength = length;
       Stream oRequestStream = oWebrequest.GetRequestStream();
    
       // Write the post header
       oRequestStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
    
       // Stream the file contents in small pieces (4096 bytes, max).
       byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)oFileStream.Length))];
       int bytesRead = 0;
       double size=0;
       while ((bytesRead = oFileStream.Read(buffer, 0, buffer.Length)) != 0)
          {
           oRequestStream.Write(buffer, 0, bytesRead);
           size+=bytesRead;
           if(uploading!=null)uploading(length ,size);
          }
        oFileStream.Close();
    
        // Add the trailing boundary
       oRequestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
       WebResponse oWResponse = oWebrequest.GetResponse();
       Stream s = oWResponse.GetResponseStream();
       StreamReader sr = new StreamReader(s);
       String sReturnString = sr.ReadToEnd();
    
        // Clean up
        oFileStream.Close();
        oRequestStream.Close();
        s.Close();
        sr.Close();
    
        return sReturnString;
    }

    如果有增加取消功能,可以调用oWebrequest的Abort方法取消上传请求。

    展开全文
  • 图片上传,是网站开发常见的需求。通常来讲就是用户上传图片啊,完事后台进行处理。比如什么压缩啊,剪裁啊,生成缩略图啊什么的。...我们有时候上传个三四M的图片就会造成内存溢出。这是为什呢??? ...

    图片上传,是网站开发常见的需求。通常来讲就是用户上传图片啊,完事后台进行处理。比如什么压缩啊,剪裁啊,生成缩略图啊什么的。在这个过程中我们有时候会遇到一个很奇怪的错误,那就是Allowed memory size。。。等等一串。

        好吧,大家都知道,这是内存溢出了。完事我们修改 php.ini 配置文件中的相关参数来解决,成效很明显,大部分情况下我们完事了,成功了。但是不知道大家注意没,它默认的上传限制不算太小,就按着64M来说吧。我们有时候上传个三四M的图片就会造成内存溢出。这是为什呢???

        拿php的gd库来说吧,它处理图片的时候会用到一个函数,imagecreatetruecolor()。网上搜索下,知道了它是用来创建高清和透明图片的。所以嘞,基本上可以确定造成内存溢出的原因了,那就是图片的分辨率太高了。

        咱们来看个公式:(图片分辨率,图片对象的width和height )X(图片的通道数,一般是3)X 1.7。

        把一张分辨率高的图片信息代入公式大家就会有所发现了,我们就会知道,占用内存的大小不是取决于图片的体积大小,而是分辨率。

    展开全文
  • 当你使用IFormFile接口来上传...这种情况下,如果上传一些小文件是没问题的,但是如果上传大文件,势必会造成服务器内存大量被占用甚至溢出,所以IFormFile接口只适合小文件上传。 一个文件上传页面的Html代码一般...

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

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

    <form method="post" enctype="multipart/form-data" action="/Upload">
        <div>
            <p>Upload one or more files using this form:</p>
            <input type="file" name="files" />
        </div>
        <div>
             <input type="submit" value="Upload" />
        </div>
    </form>

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

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

    [HttpPost]
    public async Task<IActionResult> Post(List<IFormFile> files)
    {
        long size = files.Sum(f => f.Length);
    
        foreach (var formFile in files)
        {
            var filePath = @"D:\UploadingFiles\" + formFile.FileName;
    
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }
            }
        }
                
        return Ok(new { count = files.Count, size });
    }

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

     

    用文件流 (大文件上传)

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

    Content-Type=multipart/form-data; boundary=---------------------------99614912995
    -----------------------------99614912995
    Content-Disposition: form-data; name="SOMENAME"
    
    Formulaire de Quota
    -----------------------------99614912995
    Content-Disposition: form-data; name="OTHERNAME"
    
    SOMEDATA
    -----------------------------99614912995
    Content-Disposition: form-data; name="files"; filename="Misc 001.jpg"
    
    SDFESDSDSDJXCK+DSDSDSSDSFDFDF423232DASDSDSDFDSFJHSIHFSDUIASUI+/==
    -----------------------------99614912995
    Content-Disposition: form-data; name="files"; filename="Misc 002.jpg"
    
    ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
    -----------------------------99614912995
    Content-Disposition: form-data; name="files"; filename="Misc 003.jpg"
    
    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是三个上传的图片文件。

     

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

    using System;
    using System.IO;
    using Microsoft.Net.Http.Headers;
    
    namespace AspNetCore.MultipartRequest
    {
        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 = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary);// .NET Core <2.0
                var boundary = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; //.NET Core 2.0
                if (string.IsNullOrWhiteSpace(boundary))
                {
                    throw new InvalidDataException("Missing content-type boundary.");
                }
    
                //注意这里的boundary.Length指的是boundary=---------------------------99614912995中等号后面---------------------------99614912995字符串的长度,也就是section分隔符的长度,上面也说了这个长度一般不会超过70个字符是比较合理的
                if (boundary.Length > lengthLimit)
                {
                    throw new InvalidDataException(
                        $"Multipart boundary length limit {lengthLimit} exceeded.");
                }
    
                return boundary;
            }
    
            public static bool IsMultipartContentType(string contentType)
            {
                return !string.IsNullOrEmpty(contentType)
                        && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
            }
    
            //如果section是表单键值对section,那么本方法返回true
            public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
            {
                // Content-Disposition: form-data; name="key";
                return contentDisposition != null
                        && contentDisposition.DispositionType.Equals("form-data")
                        && string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
                        && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); // For .NET Core <2.0 remove ".Value"
            }
    
            //如果section是上传文件section,那么本方法返回true
            public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
            {
                // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
                return contentDisposition != null
                        && contentDisposition.DispositionType.Equals("form-data")
                        && (!string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
                            || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value)); // For .NET Core <2.0 remove ".Value"
            }
    
            // 如果一个section的Header是: Content-Disposition: form-data; name="files"; filename="Misc 002.jpg"
            // 那么本方法返回: files
            public static string GetFileContentInputName(ContentDispositionHeaderValue contentDisposition)
            {
                return contentDisposition.Name.Value;
            }
    
            // 如果一个section的Header是: Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            // 那么本方法返回: Misc 002.jpg
            public static string GetFileName(ContentDispositionHeaderValue contentDisposition)
            {
                return contentDisposition.FileName.Value;
            }
        }
    }

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

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http.Features;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.WebUtilities;
    using Microsoft.Net.Http.Headers;
    using System;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace AspNetCore.MultipartRequest
    {
        public static class FileStreamingHelper
        {
            private static readonly FormOptions _defaultFormOptions = new FormOptions();
    
            public static async Task<FormValueProvider> StreamFiles(this HttpRequest request, string targetDirectory)
            {
                if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
                {
                    throw new Exception($"Expected a multipart request, but got {request.ContentType}");
                }
    
                // Used to accumulate all the form url encoded key value pairs in the 
                // request.
                var formAccumulator = new KeyValueAccumulator();
    
                var boundary = MultipartRequestHelper.GetBoundary(
                    MediaTypeHeaderValue.Parse(request.ContentType),
                    _defaultFormOptions.MultipartBoundaryLengthLimit);
                var reader = new MultipartReader(boundary, request.Body);
    
                var section = await reader.ReadNextSectionAsync();//用于读取Http请求中的第一个section数据
                while (section != null)
                {
                    ContentDispositionHeaderValue contentDisposition;
                    var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
    
                    if (hasContentDispositionHeader)
                    {
                        /*
                        用于处理上传文件类型的的section
                        -----------------------------99614912995
                        Content - Disposition: form - data; name = "files"; filename = "Misc 002.jpg"
    
                        ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
                        -----------------------------99614912995
                        */
                        if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                        {
                            if (!Directory.Exists(targetDirectory))
                            {
                                Directory.CreateDirectory(targetDirectory);
                            }
    
                            var fileName = MultipartRequestHelper.GetFileName(contentDisposition);
    
                            var loadBufferBytes = 1024;//这个是每一次从Http请求的section中读出文件数据的大小,单位是Byte即字节,这里设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写入下面targetFileStream的文件流中,可以根据服务器的内存大小调整这个值。这样就避免了一次加载所有上传文件的数据到服务器内存中,导致服务器崩溃。
    
                            using (var targetFileStream = System.IO.File.Create(targetDirectory + "\\" + fileName))
                            {
                                //section.Body是System.IO.Stream类型,表示的是Http请求中一个section的数据流,从该数据流中可以读出每一个section的全部数据,所以我们下面也可以不用section.Body.CopyToAsync方法,而是在一个循环中用section.Body.Read方法自己读出数据,再将数据写入到targetFileStream
                                await section.Body.CopyToAsync(targetFileStream, loadBufferBytes);
                            }
    
                        }
                        /*
                        用于处理表单键值数据的section
                        -----------------------------99614912995
                        Content - Disposition: form - data; name = "SOMENAME"
    
                        Formulaire de Quota
                        -----------------------------99614912995
                        */
                        else if (MultipartRequestHelper.HasFormDataContentDisposition(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 = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
                            var encoding = GetEncoding(section);
                            using (var streamReader = new StreamReader(
                                section.Body,
                                encoding,
                                detectEncodingFromByteOrderMarks: true,
                                bufferSize: 1024,
                                leaveOpen: true))
                            {
                                // The value length limit is enforced by MultipartBodyLengthLimit
                                var value = await streamReader.ReadToEndAsync();
                                if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                                {
                                    value = String.Empty;
                                }
                                formAccumulator.Append(key.Value, value); // For .NET Core <2.0 remove ".Value" from key
    
                                if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
                                {
                                    throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
                                }
                            }
                        }
                    }
    
                    // Drains any remaining section body that has not been consumed and
                    // reads the headers for the next section.
                    section = await reader.ReadNextSectionAsync();//用于读取Http请求中的下一个section数据
                }
    
                // Bind form data to a model
                var formValueProvider = new FormValueProvider(
                    BindingSource.Form,
                    new FormCollection(formAccumulator.GetResults()),
                    CultureInfo.CurrentCulture);
    
                return formValueProvider;
            }
    
            private static Encoding GetEncoding(MultipartSection section)
            {
                MediaTypeHeaderValue mediaType;
                var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
                // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
                // most cases.
                if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
                {
                    return Encoding.UTF8;
                }
                return mediaType.Encoding;
            }
        }
    }

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

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var formValueProviderFactory = context.ValueProviderFactories
                .OfType<FormValueProviderFactory>()
                .FirstOrDefault();
            if (formValueProviderFactory != null)
            {
                context.ValueProviderFactories.Remove(formValueProviderFactory);
            }
     
            var jqueryFormValueProviderFactory = context.ValueProviderFactories
                .OfType<JQueryFormValueProviderFactory>()
                .FirstOrDefault();
            if (jqueryFormValueProviderFactory != null)
            {
                context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
            }
        }
     
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }

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

    [HttpPost]
    [DisableFormValueModelBinding]
    public async Task<IActionResult> Index()
    {
        FormValueProvider formModel;
        formModel = await Request.StreamFiles(@"D:\UploadingFiles");
    
        var viewModel = new MyViewModel();
    
        var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
            valueProvider: formModel);
    
        if (!bindingSuccessful)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
        }
    
        return Ok(viewModel);
    }

    视图模型viewModel的定义如下:

    public class MyViewModel
    {
        public string Username { get; set; }
    }

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

    <form method="post" enctype="multipart/form-data" action="/Home/Index">
        <div>
            <p>Upload one or more files using this form:</p>
            <input type="file" name="files" multiple />
        </div>
        <div>
            <p>Your Username</p>
            <input type="text" name="username" />
        </div>
        <div>
             <input type="submit" value="Upload" />
        </div>
    </form>

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

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

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

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.webServer>
        <security>
          <requestFiltering>
            <!--单位:字节。 -->
            <requestLimits maxAllowedContentLength="1073741824" />
            <!-- 1 GB -->
          </requestFiltering>
        </security>
      </system.webServer>
    </configuration>

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

        public void ConfigureServices(IServiceCollection services)
        {
            //设置接收文件长度的最大值。
            services.Configure<FormOptions>(x =>
            {
                x.ValueLengthLimit = int.MaxValue;
                x.MultipartBodyLengthLimit = int.MaxValue;
                x.MultipartHeadersLengthLimit = int.MaxValue;
            });
    
            services.AddMvc();
        }

     

    转载于:https://www.cnblogs.com/liuxiaoji/p/10266609.html

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

    用文件模型绑定接口:IFormFile (小文件上传)


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

     

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

    <form method="post" enctype="multipart/form-data" action="/Upload">
        <div>
            <p>Upload one or more files using this form:</p>
            <input type="file" name="files" />
        </div>
        <div>
             <input type="submit" value="Upload" />
        </div>
    </form>

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

     

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

    [HttpPost]
    public async Task<IActionResult> Post(List<IFormFile> files)
    {
        long size = files.Sum(f => f.Length);
    
        foreach (var formFile in files)
        {
            var filePath = @"F:\UploadingFiles\" + formFile.FileName.Substring(formFile.FileName.LastIndexOf("\\") + 1);//注意formFile.FileName包含上传文件的文件路径,所以要进行Substring只取出最后的文件名
    
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }
            }
        }
    
        return Ok(new { count = files.Count, size });
    }

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

     

    不要直接用Request.Form.Files

    上面例子是我们知道Input type="file"标签的name属性值时的情况,如果你不知道Input type="file"标签的name属性值(例如前端用javascript动态生成的Input type="file"标签),有什么办法可以获取所有的上传文件吗?

    也许有同学会想到可以用Request.Form.Files来获取当前Http请求中,所有的上传文件,如下所示:

    [HttpPost]
    public async Task<IActionResult> Post()
    {
        IFormFileCollection files = Request.Form.Files;
        long size = files.Sum(f => f.Length);
    
        foreach (var formFile in files)
        {
            var filePath = @"F:\UploadingFiles\" + formFile.FileName.Substring(formFile.FileName.LastIndexOf("\\") + 1);
    
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }
            }
        }
    
        return Ok(new { count = files.Count, size });
    }

    然后执行上面的代码你会发现,代码执行到Request.Form.Files的时候,就一直卡住了,如下所示:

    然后现在我们给Post方法随便加一个参数string parameter,如下所示:

    [HttpPost]
    public async Task<IActionResult> Post(string parameter)
    {
        IFormFileCollection files = Request.Form.Files;
        long size = files.Sum(f => f.Length);
    
        foreach (var formFile in files)
        {
            var filePath = @"F:\UploadingFiles\" + formFile.FileName.Substring(formFile.FileName.LastIndexOf("\\") + 1);
    
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }
            }
        }
    
        return Ok(new { count = files.Count, size });
    }

    运行代码,你会发现虽然Post方法的参数string parameter没得到任何值为null,但是这次Post方法却没有卡在Request.Form.Files,文件上传成功,Post方法成功执行完毕:

    这是因为当ASP.NET Core MVC中Controller的Action方法没有定义参数的时候,Request.Form不会做数据绑定,也就是说当我们在上面Post方法没有定义参数的时候,Request.Form根本就没有被ASP.NET Core初始化,所以只要一访问Request.Form代码就会被卡住,所以当我们随便给Post方法定义一个string parameter参数后,Request.Form就被初始化了,这时就可以访问Request.Form中的数据了。

    既然必须要给Post方法定义参数,那我们就定义有意义的参数,而不是胡乱定义一个没有用的。我们将Post方法的代码改为如下:

    [HttpPost]
    public async Task<IActionResult> Post([FromForm]IFormCollection formData)
    {
        IFormFileCollection files = formData.Files;//等价于Request.Form.Files
    
        long size = files.Sum(f => f.Length);
    
        foreach (var formFile in files)
        {
            var inputName = formFile.Name;//可以通过IFormFile.Name属性获得每个上传文件,在页面上所属Input type="file"标签的name属性值
            var filePath = @"F:\UploadingFiles\" + formFile.FileName.Substring(formFile.FileName.LastIndexOf("\\") + 1);
    
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }
            }
        }
    
        return Ok(new { count = files.Count, size });
    }

    我们给Post方法定义了一个IFormCollection类型的参数formData,并且标记了[FromForm]特性标签,表示IFormCollection formData参数使用Http请求中的表单(Form)数据进行初始化,所以这下formData其实就等价于Request.Form了。

    我们可以从formData中访问表单(Form)提交的任何数据,获得所有的上传文件。其实Post方法的参数名字叫什么并不重要(本例中我们取名为formData),但是其参数必须是IFormCollection类型才会绑定Http请求中的表单(Form)数据,这才是关键。

    执行上面的代码,文件成功上传,代码成功执行完毕:

     

     

    用文件流 (大文件上传)


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

    Content-Type=multipart/form-data; boundary=---------------------------99614912995
    -----------------------------99614912995
    Content-Disposition: form-data; name="SOMENAME"
    
    Formulaire de Quota
    -----------------------------99614912995
    Content-Disposition: form-data; name="OTHERNAME"
    
    SOMEDATA
    -----------------------------99614912995
    Content-Disposition: form-data; name="files"; filename="Misc 001.jpg"
    
    SDFESDSDSDJXCK+DSDSDSSDSFDFDF423232DASDSDSDFDSFJHSIHFSDUIASUI+/==
    -----------------------------99614912995
    Content-Disposition: form-data; name="files"; filename="Misc 002.jpg"
    
    ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
    -----------------------------99614912995
    Content-Disposition: form-data; name="files"; filename="Misc 003.jpg"
    
    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是三个上传的图片文件。

     

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

    using System;
    using System.IO;
    using Microsoft.Net.Http.Headers;
    
    namespace AspNetCore.MultipartRequest
    {
        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 = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary);// .NET Core <2.0
                var boundary = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; //.NET Core 2.0
                if (string.IsNullOrWhiteSpace(boundary))
                {
                    throw new InvalidDataException("Missing content-type boundary.");
                }
    
                //注意这里的boundary.Length指的是boundary=---------------------------99614912995中等号后面---------------------------99614912995字符串的长度,也就是section分隔符的长度,上面也说了这个长度一般不会超过70个字符是比较合理的
                if (boundary.Length > lengthLimit)
                {
                    throw new InvalidDataException(
                        $"Multipart boundary length limit {lengthLimit} exceeded.");
                }
    
                return boundary;
            }
    
            public static bool IsMultipartContentType(string contentType)
            {
                return !string.IsNullOrEmpty(contentType)
                        && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
            }
    
            //如果section是表单键值对section,那么本方法返回true
            public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
            {
                // Content-Disposition: form-data; name="key";
                return contentDisposition != null
                        && contentDisposition.DispositionType.Equals("form-data")
                        && string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
                        && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); // For .NET Core <2.0 remove ".Value"
            }
    
            //如果section是上传文件section,那么本方法返回true
            public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
            {
                // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
                return contentDisposition != null
                        && contentDisposition.DispositionType.Equals("form-data")
                        && (!string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
                            || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value)); // For .NET Core <2.0 remove ".Value"
            }
    
            // 如果一个section的Header是: Content-Disposition: form-data; name="files"; filename="Misc 002.jpg"
            // 那么本方法返回: files
            public static string GetFileContentInputName(ContentDispositionHeaderValue contentDisposition)
            {
                return contentDisposition.Name.Value;
            }
    
            // 如果一个section的Header是: Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            // 那么本方法返回: Misc 002.jpg
            public static string GetFileName(ContentDispositionHeaderValue contentDisposition)
            {
                return contentDisposition.FileName.Value;
            }
        }
    }

     

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

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Http.Features;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using Microsoft.AspNetCore.WebUtilities;
    using Microsoft.Net.Http.Headers;
    using System;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace AspNetCore.MultipartRequest
    {
        public static class FileStreamingHelper
        {
            private static readonly FormOptions _defaultFormOptions = new FormOptions();
    
            public static async Task<FormValueProvider> StreamFiles(this HttpRequest request, string targetDirectory)
            {
                if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
                {
                    throw new Exception($"Expected a multipart request, but got {request.ContentType}");
                }
    
                // Used to accumulate all the form url encoded key value pairs in the 
                // request.
                var formAccumulator = new KeyValueAccumulator();
    
                var boundary = MultipartRequestHelper.GetBoundary(
                    MediaTypeHeaderValue.Parse(request.ContentType),
                    _defaultFormOptions.MultipartBoundaryLengthLimit);
                var reader = new MultipartReader(boundary, request.Body);
    
                var section = await reader.ReadNextSectionAsync();//用于读取Http请求中的第一个section数据
                while (section != null)
                {
                    ContentDispositionHeaderValue contentDisposition;
                    var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
    
                    if (hasContentDispositionHeader)
                    {
                        /*
                        用于处理上传文件类型的的section
                        -----------------------------99614912995
                        Content - Disposition: form - data; name = "files"; filename = "Misc 002.jpg"
    
                        ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
                        -----------------------------99614912995
                        */
                        if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                        {
                            if (!Directory.Exists(targetDirectory))
                            {
                                Directory.CreateDirectory(targetDirectory);
                            }
    
                            var fileName = MultipartRequestHelper.GetFileName(contentDisposition);
    
                            var loadBufferBytes = 1024;//这个是每一次从Http请求的section中读出文件数据的大小,单位是Byte即字节,这里设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写入下面targetFileStream的文件流中,可以根据服务器的内存大小调整这个值。这样就避免了一次加载所有上传文件的数据到服务器内存中,导致服务器崩溃。
    
                            using (var targetFileStream = System.IO.File.Create(targetDirectory + "\\" + fileName))
                            {
                                //section.Body是System.IO.Stream类型,表示的是Http请求中一个section的数据流,从该数据流中可以读出每一个section的全部数据,所以我们下面也可以不用section.Body.CopyToAsync方法,而是在一个循环中用section.Body.Read方法自己读出数据(如果section.Body.Read方法返回0,表示数据流已经到末尾,数据已经全部都读取完了),再将数据写入到targetFileStream
                                await section.Body.CopyToAsync(targetFileStream, loadBufferBytes);
                            }
    
                        }
                        /*
                        用于处理表单键值数据的section
                        -----------------------------99614912995
                        Content - Disposition: form - data; name = "SOMENAME"
    
                        Formulaire de Quota
                        -----------------------------99614912995
                        */
                        else if (MultipartRequestHelper.HasFormDataContentDisposition(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 = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
                            var encoding = GetEncoding(section);
                            using (var streamReader = new StreamReader(
                                section.Body,
                                encoding,
                                detectEncodingFromByteOrderMarks: true,
                                bufferSize: 1024,
                                leaveOpen: true))
                            {
                                // The value length limit is enforced by MultipartBodyLengthLimit
                                var value = await streamReader.ReadToEndAsync();
                                if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                                {
                                    value = String.Empty;
                                }
                                formAccumulator.Append(key.Value, value); // For .NET Core <2.0 remove ".Value" from key
    
                                if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
                                {
                                    throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
                                }
                            }
                        }
                    }
    
                    // Drains any remaining section body that has not been consumed and
                    // reads the headers for the next section.
                    section = await reader.ReadNextSectionAsync();//用于读取Http请求中的下一个section数据
                }
    
                // Bind form data to a model
                var formValueProvider = new FormValueProvider(
                    BindingSource.Form,
                    new FormCollection(formAccumulator.GetResults()),
                    CultureInfo.CurrentCulture);
    
                return formValueProvider;
            }
    
            private static Encoding GetEncoding(MultipartSection section)
            {
                MediaTypeHeaderValue mediaType;
                var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
                // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
                // most cases.
                if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
                {
                    return Encoding.UTF8;
                }
                return mediaType.Encoding;
            }
        }
    }

     

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

    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.AspNetCore.Mvc.ModelBinding;
    using System;
    using System.Linq;
    
    namespace AspNetCore.MultipartRequest
    {
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
        public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
        {
            public void OnResourceExecuting(ResourceExecutingContext context)
            {
                var formValueProviderFactory = context.ValueProviderFactories
                    .OfType<FormValueProviderFactory>()
                    .FirstOrDefault();
                if (formValueProviderFactory != null)
                {
                    context.ValueProviderFactories.Remove(formValueProviderFactory);
                }
    
                var jqueryFormValueProviderFactory = context.ValueProviderFactories
                    .OfType<JQueryFormValueProviderFactory>()
                    .FirstOrDefault();
                if (jqueryFormValueProviderFactory != null)
                {
                    context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
                }
            }
    
            public void OnResourceExecuted(ResourceExecutedContext context)
            {
            }
        }
    }

     

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

    [HttpPost]
    [DisableFormValueModelBinding]
    public async Task<IActionResult> Index()
    {
        FormValueProvider formModel;
        formModel = await Request.StreamFiles(@"F:\UploadingFiles");
    
        var viewModel = new MyViewModel();
    
        var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
            valueProvider: formModel);
    
        if (!bindingSuccessful)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
        }
    
        return Ok(viewModel);
    }

     

    视图模型viewModel的定义如下:

    public class MyViewModel
    {
        public string Username { get; set; }
    }

     

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

    <form method="post" enctype="multipart/form-data" action="/Home/Index">
        <div>
            <p>Upload one or more files using this form:</p>
            <input type="file" name="files" multiple />
        </div>
        <div>
            <p>Your Username</p>
            <input type="text" name="username" />
        </div>
        <div>
             <input type="submit" value="Upload" />
        </div>
    </form>

     

    这就是所有的代码,希望对大家有所帮助!

     

    参考文献:

    File uploads in ASP.NET Core

    Uploading Files In ASP.net Core

    What is the boundary parameter in an HTTP multi-part (POST) Request?

     

    转载于:https://www.cnblogs.com/OpenCoder/p/9785031.html

    展开全文
  • android 大文件分割上传(分块上传

    万次阅读 2015-02-05 23:23:06
    由于android自身的原因,对大文件(如影视频文件)的操作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题。 文件分割后分多次请求服务。 1 //文件分割上传 2 public void ...
  • 文件上传easyExcel

    千次阅读 2019-10-24 16:29:11
    但是当 Excel 的数据量非常大的时候,你也许发现,POI 是将整个 Excel 的内容全部读出来放入到内存中,所以内存消耗非常严重,如果同时进行包含大数据量的 Excel 读操作,很容易造成内存溢出问题 但 EasyExcel 的...
  • Android下大文件分割上传

    千次阅读 2013-05-02 11:31:42
    由于android自身的原因,对大文件(如影视频文件)的操作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题。 文件分割后分多次请求服务。 1 //文件分割上传 2 public ...
  • Http大文件分段上传

    千次阅读 2017-03-30 19:08:32
    由于Android自身的原因,对大文件(如视频文件)的操作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题。 文件分割后分多次请求服务。 [java] view plain copy ...
  • 由于android自身的原因,对大文件(如影视频文件)的操作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题。 文件分割后分多次请求服务。 1 //文件分割上传 2 public...
  • WebAPI上传文件

    2015-07-25 05:25:00
    今天在研究WebAPI的上传与下载,作为Rest的框架,更多是面向资源,就其本身来说,是不会涉及也不应该涉及到大文件的处理,具体多大呢,也就是ASP.NET的限值2G。...如果并发数过多,肯定会造成内存溢出,所...
  • 上传文件对于开发者来说是一件令人头痛的问题,特别是移动开发端,今天我就来分享一种android客户端快速上传文件到java服务器的方法但是这种方法只适合于小文件上传,如果是比较大的文件可能会造成内存溢出问题(这...
  • 由于android自身的原因,对大文件(如影视频文件)的*作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题。 文件分割后分多次请求服务。  1 //文件分割上传  2 public ...
  • 该漏洞存在于对应设备的httpd组件中,在处理配置文件上传请求时,由于对请求内容的处理不当,在后续申请内存空间时存在整数溢出问题,从而造成溢出问题。攻击者利用这一漏洞可以在目标设备上实现代码执行,且无需...

空空如也

空空如也

1 2 3
收藏数 41
精华内容 16
关键字:

文件上传造成内存溢出