需求背景:
在需要通過服務端請求傳遞文件二進制文件流數據到相關的服務端保存時,如對接第三方接口很多情況下都會提供一個上傳文件的接口,但是當你直接通過前端Ajax的方式將文件流上傳到對方提供的接口的時候往往都會存在跨域的情況,這時候我們就需要通過服務端提交文件流來解決這個跨域的情況。本篇的主角就是使用HttpClient進行Http請求,提交二進制文件流到文件服務器中。
HttpClient簡單介紹:
HttpClient類實例充當發送 HTTP 請求的會話。 HttpClient實例是對該實例執行的所有請求應用的設置的集合。 此外,每個 HttpClient 實例都使用其自己的連接池,並從其他實例所執行的請求隔離其請求 HttpClient 。
使用注意點:HttpClient對象比較特殊,雖然繼承了IDisposable這個接口但是它可以被共享實例,並且使用完不能立即關閉連接、性能消耗嚴重。所以我們在使用的時候,需要主動調用Dispose方法來釋放它。可以使用using如下所示:
using(var client = new HttpClient()) { //do something with http client }
網上說.NET Core版本的HttpClient存在比較多的問題(不過我自己一直在使用HttpClient做一些http請求),大家也可以HttpClientFactory,ASP.NET Core中使用HttpClientFactory官方教程:
前端使用Ajax-FormData對象上傳文件:
注意點:
FormData:對象用以將數據編譯成鍵值對,以便用
XMLHttpRequest來發送數據。其主要用於發送表單數據,但亦可用於發送帶鍵數據(keyed data),而獨立於表單使用。contentType:需設置為false,在Ajax中contentType 設置為false 是為了避免 JQuery 對其操作,從而失去分界符,而使服務器不能正常解析文件。
processData:需設置為false,默認為true,表示以對象的形式上傳的時候會默認把對象轉化為字符串的形式上傳。
<div class="text-center">
<p><input type="file" id="imageFile" onchange="uploadImage(this)" /></p>
</div>
<div id="imageBox">
</div>
<script type="text/javascript">
//圖片上傳
function uploadImage(fileObject) {
var formData = new FormData();
var files = $(fileObject).prop('files'); //獲取到文件列表【$("#imageFile").get(0)通過id獲取文件列表】
formData.append("files", files[0]);//圖片文件流
console.log('formData=>>>', formData, files);
$.ajax({
async: true,
url:"@Url.Action("UploadImage", "ImageFileManage")",
type: 'post',
data: formData,
//https://segmentfault.com/a/1190000007207128?utm_source=tag-newest
//在 ajax 中 contentType 設置為 false 是為了避免 JQuery 對其操作,從而失去分界符,而使服務器不能正常解析文件
contentType: false,
//告訴jQuery不要去處理發送的數據
processData: false,
success: function (res) {
console.log(res);
if (res.code == 0) {
$("#imageBox").append("<img width='100' height='100' src=" + res.msg.completeFilePath+">");
}
}
});
}
</script>
接收Ajax傳遞的文件流,並轉化為轉化字節類型:
/// <summary> /// 圖片文件管理 /// </summary> public class ImageFileManageController : Controller { /// <summary> /// 接收Ajax傳遞的文件流 /// </summary> /// <param name="files">表單文件信息</param> /// <returns></returns> public IActionResult UploadImage(IFormFile files) { //var files = Request.Form.Files[0];//獲取請求發送過來的文件 if (files.Length <= 0) return Json(new { code = 1, msg = "請選擇需要上傳的文件~" }); var fileBytes = ReadFileBytes(files); var fileExtension = Path.GetExtension(files.FileName);//獲取文件格式,拓展名 var result = HttpClientHelper._.HttpClientPost("https://localhost:44347/FileUpload/SingleFileUpload", fileBytes, fileExtension, files.FileName); var resultObj = JsonConvert.DeserializeObject<UploadReponse>(result); if (resultObj.IsSuccess) { return Json(new { code = 0, msg = resultObj }); } else { return Json(new { code = 1, msg = resultObj.ReturnMsg }); } } /// <summary> /// 文件流類型轉化字節類型 /// </summary> /// <param name="fileData">表單文件信息</param> /// <returns></returns> private byte[] ReadFileBytes(IFormFile fileData) { byte[] data; using (Stream inputStream = fileData.OpenReadStream())//讀取上傳文件的請求流 { MemoryStream memoryStream = inputStream as MemoryStream; if (memoryStream == null) { memoryStream = new MemoryStream(); inputStream.CopyTo(memoryStream); } data = memoryStream.ToArray(); } return data; } } /// <summary> /// 上傳響應模型 /// </summary> public class UploadReponse { /// <summary> /// 是否成功 /// </summary> public bool IsSuccess { get; set; } /// <summary> /// 結果 /// </summary> public string ReturnMsg { get; set; } /// <summary> /// 完整地址 /// </summary> public string CompleteFilePath { get; set; } }
向目標地址提交圖片文件參數數據(HttpClient-上傳multipart/form-data內容類型):
注意:


/// <summary> /// Http網絡請求幫助類 /// </summary> public class HttpClientHelper { private static HttpClientHelper _httpClientHelper; public static HttpClientHelper _ { get => _httpClientHelper ?? (_httpClientHelper = new HttpClientHelper()); set => _httpClientHelper = value; } /// <summary> /// 向目標地址提交圖片文件參數數據 /// </summary> /// <param name="requestUrl">請求地址</param> /// <param name="bmpBytes">圖片字節流</param> /// <param name="imgType">上傳圖片類型</param> /// <param name="fileName">圖片名稱</param> /// <returns></returns> public string HttpClientPost(string requestUrl, byte[] bmpBytes, string imgType, string fileName) { using (var httpClient = new HttpClient()) { List<ByteArrayContent> byteArrayContents = new List<ByteArrayContent>(); var imgTypeContent = new ByteArrayContent(Encoding.UTF8.GetBytes(imgType)); imgTypeContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "imgType" }; byteArrayContents.Add(imgTypeContent); var fileContent = new ByteArrayContent(bmpBytes);//填充圖片文件二進制字節 fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "file", FileName = fileName }; byteArrayContents.Add(fileContent); var content = new MultipartFormDataContent(); //將ByteArrayContent集合加入到MultipartFormDataContent中 foreach (var byteArrayContent in byteArrayContents) { content.Add(byteArrayContent); } try { var result = httpClient.PostAsync(requestUrl, content).Result;//post請求 return result.Content.ReadAsStringAsync().Result; } catch (Exception ex) { return ex.Message; } } } }
模擬第三方上傳文件接口,保存圖片到服務端並返回文件預覽完整地址:
關於.NET Core上傳文件的后端服務接口可以參考我之前寫過的文章:
/// <summary> /// 單文件上傳(Ajax,Form表單都適用)模擬第三方服務端接口 /// </summary> /// <param name="file">表單文件信息</param> /// <returns></returns> public JsonResult SingleFileUpload(IFormFile file) { try { if (file != null) { var currentDate = DateTime.Now; var webRootPath = _hostingEnvironment.WebRootPath;//>>>相當於HttpContext.Current.Server.MapPath("") var filePath = $"/UploadFile/{currentDate:yyyyMMdd}/"; //創建每日存儲文件夾 if (!Directory.Exists(webRootPath + filePath)) { Directory.CreateDirectory(webRootPath + filePath); } //文件后綴 var fileExtension = Path.GetExtension(file.FileName);//獲取文件格式,拓展名 //判斷文件大小 var fileSize = file.Length; if (fileSize > 1024 * 1024 * 10) //10M TODO:(1mb=1024X1024b) { return Json(new { isSuccess = false, resultMsg = "上傳的文件不能大於10M" }); } //保存的文件名稱(以名稱和保存時間命名) var saveName = file.FileName.Substring(0, file.FileName.LastIndexOf('.')) + "_" + currentDate.ToString("HHmmss") + fileExtension; //文件保存 using (var fs = System.IO.File.Create(webRootPath + filePath + saveName)) { file.CopyTo(fs); fs.Flush(); } //完整的文件路徑 var completeFilePath = Path.Combine(filePath, saveName); return Json(new { isSuccess = true, returnMsg = "上傳成功", completeFilePath = completeFilePath }); } else { return Json(new { isSuccess = false, resultMsg = "上傳失敗,未檢測上傳的文件信息~" }); } } catch (Exception ex) { return Json(new { isSuccess = false, resultMsg = "文件保存失敗,異常信息為:" + ex.Message }); } }
項目完整示例:
參考文章:
https://www.cnblogs.com/willick/p/net-core-httpclient.html
https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient?view=net-5.0
