Netcore webapi + 後端多檔案多引數 multipart/form-data 上傳
這次是因為專案上需要用到多檔案上傳功能,需求是有一個winfrom程式要上傳多個檔案給netcore webapi 並且上傳介面要能夠支援多個引數的傳遞方式;
期間也遇到了很多問題,隨手記錄一下,方便自己也也方便他人;
首先第一步要搞清楚什麼是 multipart/form-data 格式上傳 ;
這裡我寫了個表單,然後用抓包工具來抓取協議;
表單提交頁面如下:
抓包的請求頭資訊
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryagmh7QIcWZuIziUf
----WebKitFormBoundaryagmh7QIcWZuIziUf #這是用來做邊界的我稱之為分界符;
Content-Type: multipart/form-data; #這就是提交的方式啦;
提交後抓包的總體結構資訊資訊
由圖可見引數格式是如何構成的;
不難看出 ------WebKitFormBoundaryagmh7QIcWZuIziUf 這是個分界線用於和下一個引數資料分隔;
每一行引數格式定義為 如下圖:
------WebKitFormBoundaryagmh7QIcWZuIziUf 【換行】 #分界符
Content-Disposition: form-data; name="引數欄位名稱" 【換行】
【換行】
下面看看程式碼如何實現的 api服務端程式碼
/// <summary>
/// 訂單儲存物件
/// </summary>
public class OrderSaveEntity : Add_ChenryOrder_Model
{
public List<IFormFile> Files { get; set; }
}
/// <summary>
/// 提交儲存訂單並上傳資訊
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
[HttpPost, DisableRequestSizeLimit] //DisableRequestSizeLimit 上傳檔案不限制檔案大小
public async Task<Result> SaveOrder([FromForm] OrderSaveEntity m)
{
var files = m.Files;
//base._httpContextAccessor.HttpContext.Request.Form.Files; 你也可以不用定義 List<IFormFile> 直接用這個方式獲取上傳的檔案,但需要注入 httpContextAccessor 物件
if (m == null) return Result.Error("上傳內容錯誤");
if (!files.Any()) return Result.Error("沒有檢測到上傳資料");
string webroot = hostingEnvironment.WebRootPath;//拿到 wwwroot 路徑
string spac = Path.DirectorySeparatorChar.ToString();//不同的系統會有不同的目錄碟符 如 微軟的系統為\ LINXU系統為/
//定義儲存路徑
string localpath = $"{spac}{token.UserId}{spac}{m.BuessID}{spac}";//自己定義的儲存目錄結構 相對路徑【看個人業務,如不需要請忽略,我這裡是需要存相對路徑的】
string path = $"{webroot}{spac}{localpath}"; //絕對儲存路徑
//批量上傳
files.ForEach(x =>
{
string ext = Path.GetExtension(x.FileName);//檔案字尾
string filename = Guid.NewGuid().ToString("N");//自定義檔名
var fullsavepath = $"{path}{filename}{ext}";
FileUpLoad(x, fullsavepath);
});
m.Source = localpath; //路徑
m.Uid = base.Token.UserId;//使用者ID
m.FileCount = fcount;//檔案數量
await chenryOrderService.SaveData(m);//提交業務層儲存
return Result.Ok($"上傳成功!總計:{ files.Count}個檔案。");
}
/// <summary>
/// 通用上傳方法 PS : 該方法不建議使用 Task 方式,以免引發多個檔案上傳執行緒對流的管道被共享報錯
/// </summary>
/// <param name="file">IFormFile 物件</param>
/// <param name="savepath">儲存的檔案完整物理路徑</param>
[NonAction]
public void FileUpLoad(IFormFile file, string savepath)
{
var index = savepath.LastIndexOf(Path.DirectorySeparatorChar);//拿到最後一個目錄位置
var dir = savepath.Substring(0, index);//擷取到最後一個目錄位置
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); //判斷目錄是否建立不存在則建立
//從IFromFile檔案流上載到伺服器目錄上
using (var stream = new FileStream(savepath, FileMode.Create))
{
file.CopyTo(stream);
}
}
C#客戶端上傳程式碼:
public const string ApiSite = "http://localhost:56253";//介面域名
public const string Token = "U0pfVG9rZW5fMjRiZGViYjNmZmRlNGYxOGI0MTZkNzhkMTFjNDlmZWU="; //測試token
static void Main(string[] args)
{
//即將上傳的檔案根路徑
string filepath = Path.Combine(AppContext.BaseDirectory, "files");
//鍵值物件引數後續提交使用 【文字型引數 PS 測試只加了必填項】
Dictionary<string, object> kys = new Dictionary<string, object>();
kys.Add("BuessID", "1505022b92ec482782f11f2a4108f003");
kys.Add("OrderNum", "2020402193203958");
kys.Add("Name", "測試一下有沒有更新");
//檔案引數鍵值物件 key 檔名 value 檔案所在本地路徑
Dictionary<string, string> files = new Dictionary<string, string>();
for (int i = 1; i <= 3; i++) //上傳模型3個 圖片 + 模型檔案
{
files.Add($"{i}.jpg", $"{Path.Combine(filepath, i.ToString())}.jpg");//key :圖片名 value :圖片路徑
files.Add($"{i}.obj", $"{Path.Combine(filepath, i.ToString())}.obj");//key :檔名 value :檔案路徑
}
//上傳介面地址
string url = $"{ApiSite}/DesignOrder/SaveOrder";
var webRequest = HttpWebRequest.Create(url);
webRequest.Method = "POST"; //POST提交方式
webRequest.Timeout = 60000; //請求超時時間
webRequest.Headers.Add("token", Token); // ** token 必填
// 邊界符 定義
var boundary = "------WebKitFormBoundary" + DateTime.Now.Ticks.ToString("x");
webRequest.ContentType = "multipart/form-data; boundary=" + boundary; //form-data 形式的請求頭型別
//用於列印的可以忽略
string start = "--" + boundary + "\r\n";
string end = "--" + boundary + "--\r\n";
string newline = "\r\n";
//寫入流的固定格式值
var beginBoundary = Encoding.ASCII.GetBytes("--" + boundary + "\r\n"); // 開始邊界符
var endBoundary = Encoding.ASCII.GetBytes("--" + boundary + "--\r\n"); // 結束結束符
var newLineBytes = Encoding.UTF8.GetBytes("\r\n"); //換行符
//區分上傳型別 獲得 content 型別
Func<string, string> getcontent = (s) =>
{
var ex = Path.GetExtension(s); //拿到字尾
return FileContentType.GetMimeType(ex); //請求字尾返回對應的 type型別
//PS 獲取Content-Type 的 三種方法傳送門:https://blog.csdn.net/a873744779/article/details/100514010
};
using (var stream = new MemoryStream())
{
// 寫入欄位引數
var keyValue = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
// 裝載form表單欄位【非上傳檔案欄位】
foreach (string key in kys.Keys)
{
//引數欄位轉位元組
var keyValueBytes = Encoding.UTF8.GetBytes(string.Format(keyValue, key, kys[key]));
stream.Write(beginBoundary, 0, beginBoundary.Length);//寫入邊界開始
stream.Write(keyValueBytes, 0, keyValueBytes.Length);//寫入位元組
stream.Write(newLineBytes, 0, newLineBytes.Length);//寫入換行符
//列印日誌
Console.Write(start);
Console.Write(string.Format(keyValue, key, kys[key]));
}
//多檔案上傳
foreach (var item in files)
{
var fileData = File.ReadAllBytes(item.Value); //讀檔案流
// 寫入檔案 name = \"Files\" 這裡對應的是介面引數 List<IfromFile> Files
var fileHeader = "Content-Disposition: form-data; name=\"Files\"; filename=\"" + item.Key + "\"\r\n"
+ "Content-Type: " + getcontent(item.Key) + "\r\n\r\n";
var fileHeaderBytes = Encoding.UTF8.GetBytes(fileHeader);
stream.Write(beginBoundary, 0, beginBoundary.Length);// 寫入開始邊界
stream.Write(fileHeaderBytes, 0, fileHeaderBytes.Length);//寫入檔案格式物件流
stream.Write(fileData, 0, fileData.Length); // 寫入檔案流物件
stream.Write(newLineBytes, 0, newLineBytes.Length);//寫入換行符
//列印日誌
Console.Write(start);
Console.Write(fileHeader);
Console.Write(newline);
}
Console.WriteLine(end);//列印結束邊界
// 寫入結束邊界符
stream.Write(endBoundary, 0, endBoundary.Length);
webRequest.ContentLength = stream.Length;//設定請求物件,位元組長度總量
stream.Position = 0;//指定流開始索引
var tempBuffer = new byte[stream.Length];//定義新位元組流物件,用於提交請求結果
stream.Read(tempBuffer, 0, tempBuffer.Length); //從0開始索引讀流到新 tempbuffer 物件
//請求結果流
using (Stream requestStream = webRequest.GetRequestStream())
{
requestStream.Write(tempBuffer, 0, tempBuffer.Length);//寫入請求流
using (var response = webRequest.GetResponse())//請求結果流
using (StreamReader httpStreamReader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) //從結果流物件中讀取結果流並設定流格式轉換為 uft8 格式
{
string result = httpStreamReader.ReadToEnd();//返回伺服器返回json
Console.WriteLine(result);//輸出結果 如有需要請 自行 json 序列化返回結果
}
}
}
}
另外可能會遇到上傳被伺服器拒絕的問題 因為檔案太大了
下面是解除限制方法:
Startup.CS 設定
public void ConfigureServices(IServiceCollection services)
{
//上傳檔案不做限制可以上傳最大
services.Configure<FormOptions>(options =>
{
options.ValueLengthLimit = int.MaxValue;
options.MultipartBodyLengthLimit = long.MaxValue;
options.MemoryBufferThreshold = int.MaxValue;
});
}
Program.cs 設定
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
//設定應用伺服器Kestrel請求體最大
options.Limits.MaxRequestBodySize = long.MaxValue;
});
webBuilder.UseStartup<Startup>().UseUrls("http://*:5000;");
});
}
還有一種方式是在 Conntroller 的 Action 上打標記
/// <summary>
/// 提交儲存訂單並上傳資訊
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
[HttpPost, DisableRequestSizeLimit] //DisableRequestSizeLimit 上傳檔案不限制檔案大小
public async Task<Result> SaveOrder([FromForm] OrderSaveEntity m)
到此結束了,第一次寫技術文章, 歡迎各界大佬的批評指正。
相關文章
- 微信小程式介面請求/form-data/單檔案、多檔案上傳微信小程式ORM
- Vue實現多檔案上傳功能(前端 + 後端程式碼)Vue前端後端
- apisix~自定義檔案上傳代理外掛~支援form-data檔案和kv引數APIORM
- laravel 多檔案上傳Laravel
- WebAPI Angularjs 上傳檔案WebAPIAngularJS
- PHP multipart/form-data 遠端DOS漏洞PHPORM
- HttpFileCollection 實現多檔案上傳HTTP
- SpringMVC檔案上傳下載(單檔案、多檔案)SpringMVC
- postman測試多檔案上傳,並且後臺接收檔案陣列Postman陣列
- angular上傳圖片到.netcore後端AngularNetCore後端
- SpringMVC多個檔案上傳實現SpringMVC
- HttpClient多檔案上傳程式碼及普通引數中文亂碼問題解決HTTPclient
- net6 WebAPI大檔案上傳WebAPI
- Java大檔案上傳、分片上傳、多檔案上傳、斷點續傳、上傳檔案minio、分片上傳minio等解決方案Java斷點
- SpringMVC實現多檔案上傳原始碼SpringMVC原始碼
- struts動態多檔案上傳實現
- LayUI多檔案上傳,支援歷史上傳預覽UI
- el-upload 檔案上傳帶引數
- PHP實現單檔案、多檔案上傳 封裝 物件導向實現檔案上傳PHP封裝物件
- mybatis 傳入多個引數MyBatis
- VUE-多檔案斷點續傳、秒傳、分片上傳Vue斷點
- 記一個 FormData 多檔案上傳問題ORM
- Netcore webapi action swagger response返回引數使用匿名型別NetCoreWebAPISwagger型別
- Asp.net WebApi 傳遞json資料以及上傳檔案ASP.NETWebAPIJSON
- spring-boot-route(三)實現多檔案上傳Springboot
- Laravel 檔案上傳和獲取請求引數Laravel
- 檔案上傳之後端黑白名單繞過後端
- SpringBoot基礎實戰系列(三)springboot單檔案與多檔案上傳Spring Boot
- 12.MyBatis學習--對映檔案_引數處理_單個引數&多個引數&命名引數MyBatis
- SpringMVC【引數繫結、資料回顯、檔案上傳】SpringMVC
- 通過反射獲取上傳檔案方法引數中的檔名反射
- Mybatis parameterType 傳入多個引數的使用MyBatis
- .NET和.NET Core Web APi FormData多檔案上傳對比WebAPIORM
- 理解HTTP協議中的multipart/form-dataHTTP協議ORM
- 客戶端解析伺服器響應的multipart/form-data資料客戶端伺服器ORM
- vue+springboot檔案上傳下載(前後端分離)VueSpring Boot後端
- 【多檔案自平衡雲傳輸】使用展示 —— 檔案傳輸系統
- 解決表格檔案上傳無法刪除臨時檔案的問題Failed to perform cleanup of multipart itemsAIORM