日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET Core文件上传IFormFile于Request.Body的羁绊

發布時間:2023/12/4 asp.net 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core文件上传IFormFile于Request.Body的羁绊 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

????在上篇文章深入探究ASP.NET Core讀取Request.Body的正確方式[1]中我們探討了很多人在日常開發中經常遇到的也是最基礎的問題,那就是關于Request.Body的讀取方式問題,看是簡單實則很容易用不好。筆者也是非常榮幸的得到了許多同學的點贊支持,心理也是非常的興奮。在此期間在技術交流群中,有一位同學看到了我的文章之后提出了一個疑問,說關于ASP.NET Core文件上傳IFormFile和Request.Body之間存在什么樣的關系。由于筆者沒對這方面有過相關的探究,也沒敢做過多回答,怕誤導了那位同學,因此私下自己研究了一番,故作此文,希望能幫助更多的同學解除心中的疑惑。

IFormFile的使用方式

考慮到可能有的同學對ASP.NET Core文件上傳操作可能不是特別的理解,接下來咱們通過幾個簡單的操作,讓大家簡單的熟悉一下。

簡單使用演示

首先是最簡單的單個文件上傳的方式

[HttpPost] public string UploadFile (IFormFile formFile) {return $"{formFile.FileName}--{formFile.Length}--{formFile.ContentDisposition}--{formFile.ContentType}"; }

非常簡單的操作,通過IFormFile實例直接獲取文件信息,這里需要注意模型綁定的名稱一定要和提交的表單值的name保持一致,這樣才能正確的完成模型綁定。還有的時候我們是要通過一個接口完成一批文件上傳,這個時候我們可以使用下面的方式

[HttpPost] public IEnumerable<string> UploadFiles(List<IFormFile> formFiles) {return formFiles.Select(i => $"{i.FileName}--{ i.Length}-{ i.ContentDisposition}--{ i.ContentType}"); }

直接將模型綁定的參數聲明為集合類型即可,同時也需要注意模型綁定的名稱和上傳文件form的name要保持一致。不過有的時候你可能連List這種集合類型也不想寫,想通過一個類就能得到上傳的文件集合,好在微軟夠貼心,給我們提供了另一個類,操作如下

[HttpPost] public IEnumerable<string> UploadFiles3(IFormFileCollection formFiles) {return formFiles.Select(i => $"{i.FileName}--{ i.Length}-{ i.ContentDisposition}--{ i.ContentType}"); }

對微軟的代碼風格有了解的同學看到名字就知道,IFormFileCollection其實也是對IFormFile集合的封裝。有時候你可能都不想使用IFormFile的相關模型綁定,可能是你怕記不住這個名字,那還有別的方式能操作上傳文件嗎?當然有,可以直接在Request表單中獲取上傳文件信息

[HttpPost] public IEnumerable<string> UploadFiles2() {IFormFileCollection formFiles = Request.Form.Files;return formFiles.Select(i => $"{i.FileName}--{ i.Length}-{ i.ContentDisposition}--{ i.ContentType}"); }

其實它的本質也是獲取到IFormFileCollection,不過這種方式更加的靈活。首先是不需要模型綁定名稱不一致的問題,其次是只要有Request的地方就可以獲取到上傳的文件信息。

操作上傳內容

如果你想保存上傳的文件,或者是直接讀取上傳的文件信息,IFormFile為我們提供兩種可以操作上傳文件內容信息的方式

?一種是將上傳文件的Stream信息Copy到一個新的Stream中?另一種是直接通過OpenReadStream的方式直接獲取上傳文件的Stream信息

兩種操作方式大致如下

[HttpPost] public async Task<string> UploadFile (IFormFile formFile) {if (formFile.Length > 0){//1.使用CopyToAsync的方式using var stream = System.IO.File.Create("test.txt");await formFile.CopyToAsync(stream);//2.使用OpenReadStream的方式直接得到上傳文件的StreamStreamReader streamReader = new StreamReader(formFile.OpenReadStream());string content = streamReader.ReadToEnd();}return $"{formFile.FileName}--{formFile.Length}--{formFile.ContentDisposition}--{formFile.ContentType}"; }

更改內容大小限制

ASP.NET Core會對上傳文件的大小做出一定的限制,默認限制大小約是2MB(以字節為單位)左右,如果超出這個限制,會直接拋出異常。如何加下來我們看一下如何修改上傳文件的大小限制通過ConfigureServices的方式直接配置FormOptions的MultipartBodyLengthLimit

public void ConfigureServices(IServiceCollection services) {services.Configure<FormOptions>(options =>{// 設置上傳大小限制256MBoptions.MultipartBodyLengthLimit = 268435456;}); }

這里只是修改了對上傳文件主題大小的限制,熟悉ASP.NET Core的同學可能知道,默認情況下Kestrel對Request的Body大小也有限制,這時候我們還需要對Kestrel的RequestBody大小進行修改,操作如下所示

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{webBuilder.ConfigureKestrel((context, options) =>{//設置Body大小限制256MBoptions.Limits.MaxRequestBodySize = 268435456;});webBuilder.UseStartup<Startup>();});

很多時候這兩處設置都需要配合著一起使用,才能達到效果,用的時候需要特別的留意一下。

源碼探究

上面我們大致演示了IFormFile的基礎操作,我們上面的演示大致劃分為兩類,一種是通過模型綁定的方式而這種方式包含了IFormFile、List<IFormFile>、IFormFileCollection三種方式 ,另一種是通過Request.Form.Files的方式,為了搞懂他們的關系,就必須從模型綁定下手。

始于模型綁定

首先我們找到關于操作FormFile相關操作模型綁定的地方在FormFileModelBinder類的BindModelAsync方法[點擊查看源碼????[2]]我們看到了如下代碼,展示的代碼刪除了部分邏輯,提取的是涉及到我們要關注的流程性的操作

public async Task BindModelAsync(ModelBindingContext bindingContext) {//獲取要綁定的參數類型var createFileCollection = bindingContext.ModelType == typeof(IFormFileCollection);//判斷模型綁定參數類型是IFormFileCollection類型或可兼容IFormFileCollection類型//其中ModelBindingHelper.CanGetCompatibleCollection是用來判斷模型綁定參數是否可以兼容IFormFileCollectionif (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection<IFormFile>(bindingContext)){return;}//判斷模型綁定參數是否是集合類型ICollection<IFormFile> postedFiles;if (createFileCollection){postedFiles = new List<IFormFile>();}else{//不是集合類型的的話,包裝成為集合類型//其中ModelBindingHelper.GetCompatibleCollection是將模型綁定參數綁包裝成集合類型postedFiles = ModelBindingHelper.GetCompatibleCollection<IFormFile>(bindingContext);}//獲取要模型綁定的參數名稱var modelName = bindingContext.IsTopLevelObject? bindingContext.BinderModelName ?? bindingContext.FieldName: bindingContext.ModelName;//給postedFiles添加值,postedFiles將承載上傳的所有文件await GetFormFilesAsync(modelName, bindingContext, postedFiles);if (postedFiles.Count == 0 &&bindingContext.OriginalModelName != null &&!string.Equals(modelName, bindingContext.OriginalModelName, StringComparison.Ordinal) &&!modelName.StartsWith(bindingContext.OriginalModelName + "[", StringComparison.Ordinal) &&!modelName.StartsWith(bindingContext.OriginalModelName + ".", StringComparison.Ordinal)){modelName = ModelNames.CreatePropertyModelName(bindingContext.OriginalModelName, modelName);await GetFormFilesAsync(modelName, bindingContext, postedFiles);}object value;//如果模型參數為IFormFileif (bindingContext.ModelType == typeof(IFormFile)){//并未獲取上傳文件相關直接返回if (postedFiles.Count == 0){return;}//集合存在則獲取第一個value = postedFiles.First();}else{//如果模型參數不為IFormFileif (postedFiles.Count == 0 && !bindingContext.IsTopLevelObject){return;}var modelType = bindingContext.ModelType;//如果模型參數為IFormFile[]則直接將postedFiles轉換為IFormFile[]if (modelType == typeof(IFormFile[])){Debug.Assert(postedFiles is List<IFormFile>);value = ((List<IFormFile>)postedFiles).ToArray();}//如果模型參數為IFormFileCollection則直接使用postedFiles初始化FileCollectionelse if (modelType == typeof(IFormFileCollection)){Debug.Assert(postedFiles is List<IFormFile>);value = new FileCollection((List<IFormFile>)postedFiles);}//其他類型則直接賦值else{value = postedFiles;}}bindingContext.Result = ModelBindingResult.Success(value); }

上面的源碼中涉及到了ModelBindingHelper模型綁定幫助類[點擊查看源碼????[3]]相關的方法,主要是封裝模型綁定公共的幫助類。涉及到的我們需要的方法邏輯,上面備注已經說明了,這里就不展示源碼了,因為它對于我們的流程來說并不核心。

上面我們看到了用于初始化綁定集合的核心操作是GetFormFilesAsync方法[點擊查看源碼????[4]]話不多說我們來直接看下它的實現邏輯

private async Task GetFormFilesAsync(string modelName,ModelBindingContext bindingContext,ICollection<IFormFile> postedFiles) {//獲取Request實例var request = bindingContext.HttpContext.Request;if (request.HasFormContentType){//獲取Request.Formvar form = await request.ReadFormAsync();//遍歷Request.Form.Filesforeach (var file in form.Files){//FileName如果未空的話不進行模型綁定if (file.Length == 0 && string.IsNullOrEmpty(file.FileName)){continue;}//FileName等于模型綁定名稱的話則添加postedFilesif (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)){postedFiles.Add(file);}}}else{_logger.CannotBindToFilesCollectionDueToUnsupportedContentType(bindingContext);} }

看到這里得到的思路就比較清晰了,由于源碼需要順著邏輯走,我們大致總結一下關于FormFile模型綁定相關

?為了統一處理方便,不管是上傳的是單個文件還是多個文件,都會被包裝成ICollection<IFormFile>集合類型?ICollection<IFormFile>集合里的值就是來自于Request.Form.Files?可綁定的類型IFormFile、List<IFormFile>、IFormFileCollection等都是由ICollection<IFormFile>里的數據初始化而來?如果模型參數類型是IFormFile實例非集合類型,那么會從ICollection<IFormFile>集合中獲取第一個?模型綁定的參數名稱要和上傳的FileName保持一致,否則無法進行模型綁定

RequestForm的Files來自何處

通過上面的模型綁定我們了解到了ICollection<IFormFile>的值來自Request.Form.Files而得到RequestForm的值是來自ReadFormAsync方法,那么我們就從這個方法入手看看RequestForm是如何被初始化的,這是一個擴展方法來自于RequestFormReaderExtensions擴展類[點擊查看源碼????[5]]大致代碼如下

public static Task<IFormCollection> ReadFormAsync(this HttpRequest request, FormOptions options,CancellationToken cancellationToken = new CancellationToken()) {// 一堆判斷邏輯由此省略var features = request.HttpContext.Features;var formFeature = features.Get<IFormFeature>();//首次請求初始化沒有Form的時候初始化一個FormFeatureif (formFeature == null || formFeature.Form == null){features.Set<IFormFeature>(new FormFeature(request, options));}//調用了HttpRequest的ReadFormAsync方法return request.ReadFormAsync(cancellationToken); }

沒啥可說的直接找到HttpRequest的ReadFormAsync方法,我們在上篇文章了解過HttpRequest抽象類默認的實現類是DefaultHttpRequest,所以我們找到DefaultHttpRequest的ReadFormAsync方法[點擊查看源碼????[6]]看一下它的實現

public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken) {return FormFeature.ReadFormAsync(cancellationToken); }

從代碼中可以看到ReadFormAsync方法的返回值值來自FormFeature的ReadFormAsync方法,找到FormFeature的定義

private IFormFeature FormFeature => _features.Fetch(ref _features.Cache.Form, this, _newFormFeature)!; //其中_newFormFeature的定義來自其中委托的r值就是DefaultHttpRequest實例 private readonly static Func<DefaultHttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r, r._context.FormOptions ?? FormOptions.Default);

通過上面這段兩段代碼我們可以看到,無論怎么兜兜轉轉,最后都來到了FormFeature這個類,而且實例化這個類的時候接受的值都是來自于DefaultHttpRequest實例,其中還包含FormOptions,看著有點眼熟,不錯上面我們設置的上傳大小限制值的屬性MultipartBodyLengthLimit正是來自這里。所有最終的單子都落到了FormFeature類的ReadFormAsync方法[點擊查看源碼????[7]]找到源碼大致如下所示

public Task<IFormCollection> ReadFormAsync() => ReadFormAsync(CancellationToken.None); public Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken) {if (_parsedFormTask == null){if (Form != null){_parsedFormTask = Task.FromResult(Form);}else{_parsedFormTask = InnerReadFormAsync(cancellationToken);}}return _parsedFormTask; }

最終指向了InnerReadFormAsync這個方法,而這個方法正是初始化Form的所在,也就是說涉及到Form的初始化相關操作就是在這里進行的,因為這個方法的邏輯比較多所以我們只關注ContentType是multipart/form-data的邏輯,這里我們也就只保留這類的相關邏輯省去了其他的邏輯,有需要了解的同學可以自行查看源碼[點擊查看源碼????[8]]

private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken) {FormFileCollection? files = null;using (cancellationToken.Register((state) => ((HttpContext)state!).Abort(), _request.HttpContext)){var contentType = ContentType;// 判斷ContentType為multipart/form-data的時候if (HasMultipartFormContentType(contentType)){var formAccumulator = new KeyValueAccumulator();//得到boundary數據//Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit);// 把針對文件上傳的部分封裝到MultipartReadervar multipartReader = new MultipartReader(boundary, _request.Body){//Header個數限制HeadersCountLimit = _options.MultipartHeadersCountLimit,//Header長度限制HeadersLengthLimit = _options.MultipartHeadersLengthLimit,//Body長度限制BodyLengthLimit = _options.MultipartBodyLengthLimit,};//獲取下一個可解析的節點,可以理解為每一個要解析的上傳文件信息var p = await multipartReader.ReadNextSectionAsync(cancellationToken);//不為null說明已從Body解析出的上傳文件信息while (p != null){// 在這里解析內容配置并進一步傳遞它以避免重新分析if (!ContentDispositionHeaderValue.TryParse(p.ContentDisposition, out var contentDisposition)){throw new InvalidDataException("");}if (contentDisposition.IsFileDisposition()){var fileSection = new FileMultipartSection(p, contentDisposition);// 如果尚未對整個正文執行緩沖,則為文件啟用緩沖p.EnableRewind(_request.HttpContext.Response.RegisterForDispose,_options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit);// 找到結尾await p.Body.DrainAsync(cancellationToken);var name = fileSection.Name;var fileName = fileSection.FileName;FormFile file;//判斷Body默認的流是否被修改過,比如開啟緩沖就會修改//如果Body不是默認流則直接服務Bodyif (p.BaseStreamOffset.HasValue){file = new FormFile(_request.Body, p.BaseStreamOffset.GetValueOrDefault(), p.Body.Length, name, fileName);}else{// 如果沒有被修改過則獲取MultipartReaderStream的實例file = new FormFile(p.Body, 0, p.Body.Length, name, fileName);}file.Headers = new HeaderDictionary(p.Headers);//如果解析出來了文件信息則初始化FormFileCollectionif (files == null){files = new FormFileCollection();}if (files.Count >= _options.ValueCountLimit){throw new InvalidDataException("");}files.Add(file);}else if (contentDisposition.IsFormDisposition()){var formDataSection = new FormMultipartSection(p, contentDisposition);var key = formDataSection.Name;var value = await formDataSection.GetValueAsync();formAccumulator.Append(key, value);if (formAccumulator.ValueCount > _options.ValueCountLimit){throw new InvalidDataException("");}}else{//沒解析出來類型}p = await multipartReader.ReadNextSectionAsync(cancellationToken);}if (formAccumulator.HasValues){formFields = new FormCollection(formAccumulator.GetResults(), files);}}}// 如果可重置,則恢復讀取位置為0(因為Body被讀取到了尾部)if (_request.Body.CanSeek){_request.Body.Seek(0, SeekOrigin.Begin);}//通過files得到FormCollectionif (files != null){Form = new FormCollection(null, files);}return Form; }

這部分源碼比較多,而且這還是精簡過只剩下ContentType為multipart/form-data的內容,不過從這里我們就可以看出來FormFile的實例確實是依靠Request的Body里。其核心就在MultipartReader類的ReadNextSectionAsync方法返回的Section數據[點擊查看源碼????[9]]通過上面的循環可以看到它是循環讀取的,它通過解析Request信息持續的迭代MultipartSection信息,這種操作方式正是處理一次上傳存在多個文件的情況,具體操作如下所示

private readonly BufferedReadStream _stream; private readonly MultipartBoundary _boundary; private MultipartReaderStream _currentStream;public MultipartReader(string boundary, Stream stream, int bufferSize) {//stream即是傳遞下來的RequestBody_stream = new BufferedReadStream(stream, bufferSize);_boundary = new MultipartBoundary(boundary, false);//創建MultipartReaderStream實例_currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = HeadersLengthLimit }; }public async Task<MultipartSection?> ReadNextSectionAsync(CancellationToken cancellationToken = new CancellationToken()) {//清空上一個節點的信息await _currentStream.DrainAsync(cancellationToken);// 如果返回了空值表示為最后一個節點if (_currentStream.FinalBoundaryFound){// 清空最后一個節點的掛載數據await _stream.DrainAsync(HeadersLengthLimit, cancellationToken);return null;}//讀取header信息var headers = await ReadHeadersAsync(cancellationToken);_boundary.ExpectLeadingCrlf = true;//組裝MultipartReaderStream實例_currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = BodyLengthLimit };//判斷流是否是原始的HttpRequestStreamlong? baseStreamOffset = _stream.CanSeek ? (long?)_stream.Position : null;//通過上面信息構造MultipartSection實例return new MultipartSection() { Headers = headers, Body = _currentStream, BaseStreamOffset = baseStreamOffset }; }

這里可以看出傳遞下來的RequestBody被構建出了MultipartReaderStream實例,即MultipartReaderStream包裝了RequestBody中的信息[點擊查看源碼????[10]]看名字也知道它也是實現了Stream抽象類

internal sealed class MultipartReaderStream : Stream { }

而且我們看到BodyLengthLimit正是傳遞給了它的LengthLimit屬性,而BodyLengthLimit正是設置限制上傳文件的大小的屬性,我們找到使用LengthLimit屬性的地方,代碼如下所示[點擊查看源碼????[11]]

private int UpdatePosition(int read) {//更新Stream的Position的值,即更新讀取位置_position += read;//繼續讀取if (_observedLength < _position){//保存已經讀取了的位置_observedLength = _position;//如果讀取了位置大于LengthLimit則拋出異常if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault()){throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");}}return read; }

從這段代碼我們可以看出,正是此方法限制了讀取的Body大小,通過我們對Stream的了解,這個UpdatePosition方法也必然會在Stream的Read方法也即是此處的MultipartReaderStream的Read方法中調用[點擊查看源碼????[12]]這樣才能起到限制的作用,大致看一下Read方法的實現代碼

public override int Read(byte[] buffer, int offset, int count) {//如果已經讀到了結尾則直接返回0if (_finished){return 0;}PositionInnerStream();var bufferedData = _innerStream.BufferedData;// 匹配boundary的讀取邊界int read;if (SubMatch(bufferedData, _boundary.BoundaryBytes, out var matchOffset, out var matchCount)){// 匹配到了可讀取的邊界讀取并返回if (matchOffset > bufferedData.Offset){read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));//返回讀取的長度正是調用的UpdatePositionreturn UpdatePosition(read);}var length = _boundary.BoundaryBytes.Length;Debug.Assert(matchCount == length);var boundary = _bytePool.Rent(length);read = _innerStream.Read(boundary, 0, length);_bytePool.Return(boundary);Debug.Assert(read == length);//讀取RequestBody信息var remainder = _innerStream.ReadLine(lengthLimit: 100);remainder = remainder.Trim();//說明讀取到了boundary的結尾if (string.Equals("--", remainder, StringComparison.Ordinal)){FinalBoundaryFound = true;}Debug.Assert(FinalBoundaryFound || string.Equals(string.Empty, remainder, StringComparison.Ordinal), "Un-expected data found on the boundary line: " + remainder);_finished = true;//返回讀取的長度0說明讀到了結尾return 0;}read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));//這里同樣是UpdatePositionreturn UpdatePosition(read); }

通過這里就可清楚的看到MultipartReaderStream的Read方法就是在解析讀取的RequestBody的FormData類型的信息,解析成我們可以直接讀取或者直接保存成文件的的原始的文件信息,它還有一個異步讀取的ReadAsync方法其實現原理類似,在這里咱們就不在展示源碼了。最后我們再來看一下MultipartSection類的實現[點擊查看源碼????[13]]我們上面知道了MultipartReaderStream才是在RequestBody中解析到文件上傳信息的關鍵所在,因此MultipartSection也就是包裝了讀取好的文件信息,我們來看一下它的代碼實現

public class MultipartSection {/// <summary>/// 從header中得到的ContentType類型/// </summary>public string? ContentType{get{if (Headers != null && Headers.TryGetValue(HeaderNames.ContentType, out var values)){return values;}return null;}}/// <summary>/// 從header中得到的ContentDisposition信息/// </summary>public string? ContentDisposition{get{if (Headers != null && Headers.TryGetValue(HeaderNames.ContentDisposition, out var values)){return values;}return null;}}/// <summary>/// 讀取到的Header信息/// </summary>public Dictionary<string, StringValues>? Headers { get; set; }/// <summary>/// 從RequestBody中解析到的Stream信息,即MultipartReaderStream或其他RequestBody實例/// </summary>public Stream Body { get; set; } = default!;/// <summary>/// 已經被讀取過的Stream位置/// </summary>public long? BaseStreamOffset { get; set; } }

不出所料,這個類正是包裝了上面一堆針對HTTP請求信息中讀取到的關于上傳的文件信息,由于上面設計到了幾個類,而且設計到了一個大致的讀取流程,為了防止同學們看起來容易蒙圈,這里咱們大致總結一下這里的讀取流程。通過上面的代碼我們了解到了涉及到的幾個重要的類MultipartReader、MultipartReaderStream、MultipartSection知道這幾個類在做什么就能明白到底是怎么通過RequestBody解析到文件信息的。大致解釋一下這幾個類在做些什么

?通過MultipartReader類的ReadNextSectionAsync方法可以得到MultipartSection的實例?MultipartSection類包含的就是解析出RequestBody里的文件相關的信息包裝起來,MultipartSection的Body屬性的值正是MultipartReaderStream的實例。?MultipartReaderStream類正是通過讀取RequestBody里的各種boundary信息轉換為原始的文件內容的Stream信息?FormFile的CopyToAsync和OpenReadStream方法都是Stream操作,而操作的Stream是來自MultipartReaderStream實例

總結

????這次的分析差不多就到這里了, 本篇文章主要討論了ASP.NET Core文件上傳操作類IFormFile與RequestBody的關系,即如果通過RequestBody得到IFormFile實例相關,畢竟是源碼設計到的東西比較多也比較散亂,我們再來大致的總結一下

?無論在Action上對IFormFile、List<IFormFile>、IFormFileCollection等進行模型綁定,其實都是來自模型綁定處理類FormFileModelBinder,而這個類正是根據Request.Form.File的處理來判斷如何進行模型綁定的。?而Request.Form.File本身其實就是IFormFileCollection類型的,它的值也正是來自對RequestBody的解析,也正是我們今天的結論File的值來自RequestBody。?從RequestBody解析到IFormFileCollection是一個過程,而IFormFileCollection實際上是IFormFile的集合類型,從RequestBody解析出來的也是單個IFormFile類型,通過不斷的迭代添加得到的IFormFileCollection集合。?而從RequestBody中解析出來上傳的文件到IFormFile涉及到了幾個核心類,即MultipartReader、MultipartReaderStream和MultipartSection。其中MultipartSection是通過MultipartReader的ReadNextSectionAsync方法得到的,里面包含了解析好的上傳文件相關信息。而MultipartSection正是包裝了MultipartReaderStream,而這個類才是真正讀取RequestBody得到可讀取的文件原始Stream的關鍵所在。

到了這里本文的全部內容就差不多結束了,希望本文能給大家帶來收獲。我覺得有時候看源碼能解決許多問題和心中的疑惑,因為我們作為程序員每天寫的也就是代碼,所以沒有比程序員直接讀取代碼能更好的了解想了解的信息了。但是讀源碼也有一定的困難,畢竟是別人的代碼,思維存在一定的偏差,更何況是一些優秀的框架,作者們的思維很可能比我們要高出很多,所以很多時候讀起來會非常的吃力,即便如此筆者也覺得讀源碼是了解框架得到框架信息的一種比較行之有效的方式。

References

[1]?深入探究ASP.NET Core讀取Request.Body的正確方式:?https://www.cnblogs.com/wucy/p/14699717.html
[2]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Mvc/Mvc.Core/src/ModelBinding/Binders/FormFileModelBinder.cs#L38
[3]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Mvc/Mvc.Core/src/ModelBinding/ModelBindingHelper.cs
[4]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Mvc/Mvc.Core/src/ModelBinding/Binders/FormFileModelBinder.cs#L142
[5]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/Http/src/RequestFormReaderExtensions.cs#L21
[6]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/Http/src/Internal/DefaultHttpRequest.cs#L166
[7]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/Http/src/Features/FormFeature.cs#L108
[8]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/Http/src/Features/FormFeature.cs#L125
[9]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/WebUtilities/src/MultipartReader.cs#L68:46
[10]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/WebUtilities/src/MultipartReaderStream.cs
[11]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/WebUtilities/src/MultipartReaderStream.cs#L148
[12]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/WebUtilities/src/MultipartReaderStream.cs#L162
[13]?點擊查看源碼????:?https://github.com/dotnet/aspnetcore/blob/v5.0.6/src/Http/WebUtilities/src/MultipartSection.cs

總結

以上是生活随笔為你收集整理的ASP.NET Core文件上传IFormFile于Request.Body的羁绊的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。