2017年10月28日 星期六

C# HttpClient WebAPI : 13. 上傳本機圖片檔案到遠端伺服器上

在上一篇文章中,我們展示了如何使用 GET 要求,從網站中下載一個圖片檔案到本機電腦上,現在,我們需要進行二進位檔案型上傳的練習,要上傳的圖片檔案,我們已將包含在 Visual Studio 專案內。


了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式


上傳本機圖片檔案到遠端伺服器上

當要從用戶端上傳檔案到後端 Web API 伺服器上,我們需要使用 multipart/form-data 編碼方式,所以,我們使用 var content = new MultipartFormDataContent() 敘述,建立一個 MultipartFormDataContent 類別物件。
接者,我們使用 File.Open 方法,得到要上傳圖片的 FileStream 物件,然後,我們建立一個 StreamContent 的物件(需要把剛剛取得的 FileStream 物件設定為建構函式的引數),設定這個物件的 Content-Type 與 Content-Disposition 描述定義,這樣,就完成了上傳圖片檔案的準備工作。
var streamContent = new StreamContent(fs);
streamContent.Headers.Add("Content-Type", "application/octet-stream");
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
content.Add(streamContent, "file", filename);
最後,使用 PostAsync 方法,把剛剛產生的 MultipartFormDataContent 物件,設定為該方法的引數即可。
public static async Task<APIResult> UploadImageAsync(string filename)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Upload";
                HttpResponseMessage response = null;


                #region  設定相關網址內容
                var fooFullUrl = $"{FooUrl}";

                // Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
                //client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Content-Type 用於宣告遞送給對方的文件型態
                //client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");

                #region 將剛剛拍照的檔案,上傳到網路伺服器上(使用 Multipart 的規範)
                // 規格說明請參考 https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
                using (var content = new MultipartFormDataContent())
                {
                    var rootPath = Directory.GetCurrentDirectory();
                    // 取得這個圖片檔案的完整路徑
                    var path = Path.Combine(rootPath, filename);

                    // 開啟這個圖片檔案,並且讀取其內容
                    using (var fs = File.Open(path, FileMode.Open))
                    {
                        var fooSt = $"My{filename}";
                        var streamContent = new StreamContent(fs);
                        streamContent.Headers.Add("Content-Type", "application/octet-stream");
                        streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
                        content.Add(streamContent, "file", filename);

                        // 上傳到遠端伺服器上
                        response = await client.PostAsync(fooFullUrl, content);
                    }
                }
                #endregion
                #endregion
                #endregion

                #region 處理呼叫完成 Web API 之後的回報結果
                if (response != null)
                {
                    if (response.IsSuccessStatusCode == true)
                    {
                        // 取得呼叫完成 API 後的回報內容
                        String strResult = await response.Content.ReadAsStringAsync();
                        fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                    }
                    else
                    {
                        fooAPIResult = new APIResult
                        {
                            Success = false,
                            Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage),
                            Payload = null,
                        };
                    }
                }
                else
                {
                    fooAPIResult = new APIResult
                    {
                        Success = false,
                        Message = "應用程式呼叫 API 發生異常",
                        Payload = null,
                    };
                }
                #endregion
            }
            catch (Exception ex)
            {
                fooAPIResult = new APIResult
                {
                    Success = false,
                    Message = ex.Message,
                    Payload = ex,
                };
            }
        }
    }

    return fooAPIResult;
}

觸發的 Web API 動作

這個範例中,將會指向 URL http://vulcanwebapi.azurewebsites.net/api/Upload ,此時,將會觸發 Web API 伺服器上的 Upload 控制器(Controller)的 public async Task<APIResult> Post(List<IFormFile> files)動作(Action),其該動作的原始碼如下所示。
由這個方法參數可以看的出來,這個 Web API 動作 (Action)方法,需要使用 POST 請求來呼叫,並且,可以接受一次上傳多個檔案的請求。(在用戶端若想要上傳多個檔案,可以建立多個 StreamContent 物件,並且將這些物件都加入到 MultipartFormDataContent 物件內即可)
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost]
public async Task<APIResult> Post(List<IFormFile> files)
{
    // https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads

    string webDatasRoot = Path.Combine(_HostingEnvironment.WebRootPath, "Datas");

    long size = files.Sum(f => f.Length);

    // full path to file in temp location

    if (files.Count > 0)
    {
        foreach (var formFile in files)
        {
            if (formFile.Length > 0)
            {
                var filePath = Path.Combine(webDatasRoot, formFile.FileName);
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }

                fooAPIResult.Success = true;
                fooAPIResult.Message = "檔案上傳成功";
                fooAPIResult.Payload = new APIData
                {
                    Id = 3000,
                    Name = "Your Name",
                    Filename = formFile.FileName
                };
            }
        }
    }
    else
    {
        fooAPIResult.Success = false;
        fooAPIResult.Message = "沒有任何檔案上傳";
        fooAPIResult.Payload = null;
    }

    return fooAPIResult;
}

進行測試

在程式進入點函式,我們直接呼叫 UploadImageAsync 方法,並且指定要上傳圖片檔案的名出,一旦該題片上傳之後,將會使用瀏覽器將這個 Web 伺服器上的圖片開起來。
static async Task Main(string[] args)
{
    await UploadImageAsync("vulcan.png");
    Process myProcess = new Process();
    try
    {
        // true is the default, but it is important not to set it to false
        myProcess.StartInfo.UseShellExecute = true;
        myProcess.StartInfo.FileName = "https://vulcanwebapi.azurewebsites.net/Datas/Myvulcan.png";
        myProcess.Start();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();
}

執行結果

這個測試將會輸出底下內容
Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    在這裡的第三行中,您將會看到 Content-Type 標示為 multipart/form-data,說明了這次 POST 請求的資料編碼封裝方式,而由於採用這樣的編碼方式,所以當然會看到這樣的 Http Header boundary="cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27",這是因為每個要傳遞的欄位資料,都需要使用這個 boundary 值在 Http Body 內進行使用,最後,我們使用了 HttpClient 類別,所以,對於 Http Header Content-Length 並不需要做任何的計算,HttpClient 會幫我們計算出來(也就是說,若這個數值計算或者寫入不正確,將會導致這次的 POST 請求無法正常完成。
    由於是要上傳圖片檔案到遠端 Web API 伺服器上,因此,在 Http Body 的部分會有很多關於該圖片檔案的編碼內容,我們在這裡僅列出一小部分做為參考之用。
POST http://vulcanwebapi.azurewebsites.net/api/Upload HTTP/1.1
Accept: application/json
Content-Type: multipart/form-data; boundary="cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27"
Host: vulcanwebapi.azurewebsites.net
Content-Length: 160584
Expect: 100-continue
Connection: Keep-Alive

--cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27
Content-Type: application/octet-stream
Content-Disposition: form-data; name="files"; filename="Myvulcan.png"

 PNG
 

IHDR              u         pHYs          +      tIME         8ǫe     tEXtAuthor    H    tEXtDescription      !#   
tEXtCopyright    :    tEXtCreation time 5             tEXtSoftware ]p :   



 b   zBO     = '   e  Gz!   b   zBO     = '        " *    zBO     = ' E        zBO     =    3  \       IEND B` 
--cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27--
  • 反應 (Response)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Mon, 23 Oct 2017 02:13:20 GMT

72
{"success":true,"message":"檔案上傳成功","payload":{"id":3000,"name":"Your Name","filename":"Myvulcan.png"}}
0

相關文章索引

C# HttpClient WebAPI 系列文章索引


了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式


沒有留言:

張貼留言