.NET Core Web API使用HttpClient提交檔案的二進位制流(multipart/form-data內容型別)

追逐時光者 發表於 2021-06-08
.Net

需求背景:

   在需要通過服務端請求傳遞檔案二進位制檔案流資料到相關的服務端儲存時,如對接第三方介面很多情況下都會提供一個上傳檔案的介面,但是當你直接通過前端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官方教程:

在 ASP.NET Core 中使用 IHttpClientFactory 發出 HTTP 請求

前端使用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內容型別):

注意:

.NET Core Web API使用HttpClient提交檔案的二進位制流(multipart/form-data內容型別)

 .NET Core Web API使用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上傳檔案的後端服務介面可以參考我之前寫過的文章:

ASP.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://github.com/YSGStudyHards/DailyLearning

參考文章:

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

https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.http.iformfile.openreadstream?view=aspnetcore-5.0#Microsoft_AspNetCore_Http_IFormFile_OpenReadStream