2017年10月30日 星期一

C# HttpClient WebAPI : 18. 套用 ProgressMessageHandler ,訂閱請求與回應的資料傳輸事件

在前面的練習中,我們有做到當要知道 HttpClient 下載一個大檔案的時候,我們可以做到這個檔案的下載處理進度;可是,當我們需要做到檔案上傳或者是多個檔案一次性的上傳或者下載、又或者當資料封裝的內容過多、雖然傳輸與接收內容不多,但是網路速度可能會過慢的情況下,我們還是期望能夠知道 HttpClient 進行 請求 Request 與 回應 Response 的當時處理進度,我們需要有這樣的事件可以訂閱,以便我們可以針對觸發這些事件之後,進行相關的反應動作。
最快速又方便的解決方案,就是使用剛剛提到的 
C# HttpClient WebAPI : 17. 有趣的 HttpClient 管道,自訂 HttpMessage Handler
 Http 訊息處理器來實現這個需求


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


套用 ProgressMessageHandler ,訂閱請求與回應的資料傳輸事件

在這裡,我們需要使用 ProgressMessageHandler 類別來幫我們做到這樣工作,這裡類別有這兩個事件:HttpReceiveProgress / HttpSendProgress,分別表示當 HttpClient 進行請求與回應網路存取行為的時候,就會適時觸發這兩個事件,此時,用戶端就可以根據這樣的觸發事件行為,做出適當的處理。
在我們這個練習中,我們將訂閱者的回應事件方法,透過委派的方式,傳送到我們要使用 HttpClient 方法內,並且,加入到訂閱事件內。
在這裡,您也可以學會如何將委派方法,傳送到別的方法內,並且加入成為事件訂閱者,這樣的程式設計技巧。
為了要使用 ProgressMessageHandler 類別,我們需要在專案內加入 System.Net.Http.Formatting.Extension 這個 NuGet 套件
我們先宣告了一個 HttpProgressDelegate 委派類別,與兩個訂閱事件函示,這兩個函示將會分別表示當 HttpClient 進行網路存取行為的時候,將會用到的 CallBack 方法,為了簡化展示,我們僅僅列印出訊息文字到螢幕上。
在這裡,我們先宣告了一個 HttpProgressDelegate 委派類別
delegate void HttpProgressDelegate(object request, HttpProgressEventArgs e);
我們也設計兩個方法,這兩個方法將會以傳入到上傳圖片與下載圖片的方法內,並且會加入到 ProgressMessageHandler.HttpReceiveProgress 與 ProgressMessageHandler.HttpSendProgress 事件內,這樣,一旦有任何符合條件的事件發生了,就會觸發這些訂閱事件方法。
private static void Program_HttpSendProgress(object sender, HttpProgressEventArgs e)
{
    Console.WriteLine($"Send : {e.ProgressPercentage}");
}

private static void Program_HttpReceiveProgress(object sender, HttpProgressEventArgs e)
{
    Console.WriteLine($"Receive : {e.ProgressPercentage}");
}
最後,我們會在上傳與下載的方法內,建立 ProgressMessageHandler 類別物件,接著,設定 Http 訊息處理器的管道連結,綁定相關事件。
ProgressMessageHandler progressMessageHandler = new ProgressMessageHandler();
progressMessageHandler.InnerHandler = handler;
progressMessageHandler.HttpReceiveProgress += new EventHandler<HttpProgressEventArgs>(onHttpResponseProgress);
progressMessageHandler.HttpSendProgress += new EventHandler<HttpProgressEventArgs>(onHttpRequestProgress);

using (HttpClient client = new HttpClient(progressMessageHandler))

進行測試

在程式進入點函式,我們直接呼叫 HttpGetAsync 這個非同步方法。
static async Task Main(string[] args)
{

    //HttpReceiveProgress += Program_HttpReceiveProgress;
    //HttpSendProgress += Program_HttpSendProgress; ;

    Console.WriteLine($"上傳圖片且有進度回報");
    await UploadImageAsync("vulcan.png", Program_HttpSendProgress, Program_HttpReceiveProgress);
    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();

    Console.WriteLine($"下載圖片且有進度回報");
    await DownloadImageAsync("vulcan.png", Program_HttpSendProgress, Program_HttpReceiveProgress);
    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();
}

執行結果

這個測試將會輸出底下內容
上傳圖片且有進度回報
Send : 0
Send : 0
Send : 2
Send : 5
Send : 7
Send : 10
Send : 12
Send : 15
Send : 17
Send : 20
Send : 23
Send : 25
Send : 28
Send : 30
Send : 33
Send : 35
Send : 38
Send : 40
Send : 43
Send : 46
Send : 48
Send : 51
Send : 53
Send : 56
Send : 58
Send : 61
Send : 63
Send : 66
Send : 68
Send : 71
Send : 74
Send : 76
Send : 79
Send : 81
Send : 84
Send : 86
Send : 89
Send : 91
Send : 94
Send : 97
Send : 99
Send : 99
Send : 100
Receive : 0
Press any key to Exist...

 下載圖片且有進度回報
Receive : 2
Receive : 3
Receive : 5
Receive : 7
Receive : 9
Receive : 11
Receive : 14
Receive : 17
Receive : 19
Receive : 20
Receive : 22
Receive : 23
Receive : 26
Receive : 28
Receive : 31
Receive : 33
Receive : 36
Receive : 39
Receive : 41
Receive : 43
Receive : 46
Receive : 47
Receive : 49
Receive : 52
Receive : 54
Receive : 57
Receive : 59
Receive : 62
Receive : 64
Receive : 67
Receive : 70
Receive : 72
Receive : 75
Receive : 77
Receive : 80
Receive : 82
Receive : 84
Receive : 86
Receive : 88
Receive : 90
Receive : 93
Receive : 95
Receive : 98
Receive : 100
Press any key to Exist...

相關文章索引

C# HttpClient WebAPI 系列文章索引


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


關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程


2017年10月29日 星期日

C# HttpClient WebAPI : 17. 有趣的 HttpClient 管道,自訂 HttpMessage Handler

在 HttpClient 類別中,使用了管道的觀念,可以讓我們再進行 HTTP 通訊協定的請求與回應行為的時候,可以由程式設計師自行擴充在請求動作前後、在回應動作前後,我們可以自行設計出更多的應用與機制。


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


自訂 HttpMessage Handler

在我們這個練習中,我們自訂了兩個 HTTP 訊息處理器 (Message Handle),分別是 MyMessageHandler1 / MyMessageHandler2,這兩個訊息處理各自模擬了:
  • MyMessageHandler1
    是個類似 HTTP 處理動作日誌的功能,也就是,我們可以在要進行下一個管道請求與回應前後,記錄下當時的 HTTP 執行狀態,在這裡,我們將簡化成為僅在螢幕上輸出一段訊息,當然,您可以擴充將這些訊息寫到檔案或者資料庫內。
  • MyMessageHandler2
    這個訊息處理器,是模擬當我們大部分的 Web API,都需要傳遞 API Key 值或者 Access Token 這類內容到後端 Web API 伺服器上的時候,我們不用在每個 HttpClient 物件內來做這些重複性的工作,可以將這些高重複性的工作轉移到訊息處理器內來。
    在這個練習中,我們會為每次的 Http 請求,透過這個訊息處理器,自動把 APIKey 這個 Http 標頭自動加入倒 Http 封包內。
class MyMessageHandler1 : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Console.WriteLine("MyMessageHandler - 準備要執行");
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        Console.WriteLine($"MyMessageHandler - 執行完畢,狀態碼為 {response.StatusCode}");
        return response;
    }
}

class MyMessageHandler2 : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Add custom functionality here, before or after base.SendAsync()
        Console.WriteLine("MyMessageHandler2 - 準備要執行");
        request.Headers.TryAddWithoutValidation("APIKey", Global.APIKey);
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        Console.WriteLine($"MyMessageHandler2 - 執行完畢,狀態碼為 {response.StatusCode}");
        return response;
    }
}

使用自訂 HttpMessage Handler

在這著 Http 呼叫中,是個很單純的 GET 請求,我們從底下的程式碼中,並沒有看到任何地方有加入 APIKey 標頭,但是,在後端的 Web API 伺服器,卻會收到我們設定 APIKey 標頭數值。
為了要使用我們自訂的兩個訊息處理器 (Http Message Handler),因此,我們建立了這兩個類別物件,並且使用了 InnerHandler 將其串接成為一個管道;也就是,當進行 Http請求的時候,這些訊息處理器的過程為:
MyMessageHandler1 > MyMessageHandler2 > HtttpClientHandler
而當進行 Http 回應的時候,則會進行這樣的訊息處理器執行過程
HtttpClientHandler > MyMessageHandler2 > MyMessageHandler1
private static async Task<APIResult> HttpGetAsync()
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        var handler1 = new MyMessageHandler1();
        var handler2 = new MyMessageHandler2();
        handler1.InnerHandler = handler2;
        handler2.InnerHandler = handler;
        using (HttpClient client = new HttpClient(handler1))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/values/CustHandler";
                //string FooUrl = $"http://localhost:53495/api/values/CustHandler";
                HttpResponseMessage response = null;

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

                // Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
                // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
                //client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Content-Type 用於宣告遞送給對方的文件型態
                // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
                //client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");

                response = await client.GetAsync(fooFullUrl);
                #endregion
                #endregion

                #region 處理呼叫完成 Web API 之後的回報結果
                if (response != null)
                {
                    if (response.IsSuccessStatusCode == true)
                    {
                        var fooCT = response.Headers.FirstOrDefault(x => x.Key == "APIKeyEcho");
                        var fooCT2 = fooCT.Value.FirstOrDefault();
                        Console.WriteLine($"APIKeyEcho={fooCT2}");

                        // 取得呼叫完成 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/values/CustHandler ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult CustHandler() 動作(Action),其該動作的原始碼如下所示。
[HttpGet("CustHandler")]
public APIResult CustHandler()
{
    APIResult foo;
    StringValues VerifyCode = "";

    this.HttpContext.Request.Headers.TryGetValue("APIKey", out VerifyCode);
    if (StringValues.IsNullOrEmpty(VerifyCode))
    {
        foo = new APIResult()
        {
            Success = false,
            Message = "API 金鑰 沒有發現",
            Payload = null
        };
        Request.HttpContext.Response.Headers.Add("APIKeyEcho", "No API Key");
    }
    else
    {
        if (VerifyCode != "123")
        {
            foo = new APIResult()
            {
                Success = false,
                Message = "API 金鑰 不正確",
                Payload = null
            };
            Response.Headers.Add("APIKeyEcho", "API Key is incorrect");
        }
        else
        {
            foo = new APIResult()
            {
                Success = true,
                Message = "API 金鑰 正確無誤",
                Payload = null
            };
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(VerifyCode);
            string echo = Convert.ToBase64String(bytes);
            Request.HttpContext.Response.Headers.Add("APIKeyEcho", echo);
        }
    }
    return foo;
}

進行測試

在程式進入點函式,我們直接呼叫 HttpGetAsync 這個非同步方法。
static async Task Main(string[] args)
{
    var foo = await HttpGetAsync();
    Console.WriteLine($"使用 Get 方法呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"");

    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();

}

執行結果

這個測試將會輸出底下內容
MyMessageHandler - 準備要執行
MyMessageHandler2 - 準備要執行
MyMessageHandler2 - 執行完畢,狀態碼為 OK
MyMessageHandler - 執行完畢,狀態碼為 OK
APIKeyEcho=MTIz
使用 Get 方法呼叫 Web API 的結果
結果狀態 : True
結果訊息 : API 金鑰 正確無誤

Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    這裡的 GET 要求,我們在使用 HttpClient 物件的時候,有使用我們自訂的訊息處理器,因此,當進行 Http 的請求與回應的時候,會使用管道的觀念,使用這些自訂訊息處理器來處理相關資訊。
    在這裡,我們在訊息處理器內,加入了傳送 APIKey Header 欄位,所以,我們從底下第二行中,看到了這樣的內容 APIKey: 123,這個 APIKey Header 欄位值為 123
GET http://vulcanwebapi.azurewebsites.net/api/values/CustHandler HTTP/1.1
Accept: application/json
APIKey: 123
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
  • 反應 (Response)
    因此,在相對應的 Web API 控制器動作方法中,將會判斷這個標頭是否存在,並且做進一步的處理與回應
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
APIKeyEcho: MTIz
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=4cbc3e777eee0146fcbb9f695794b29417cc953731f6f8f581457a1d7cd7aa14;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Sun, 29 Oct 2017 06:38:07 GMT

43
{"success":true,"message":"API 金鑰 正確無誤","payload":null}
0

相關文章索引

C# HttpClient WebAPI 系列文章索引

了解更多關於 [HttpClient Class] 的使用方式

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


關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程


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 進行非同步程式設計] 的使用方式