2017年10月19日 星期四

C# HttpClient WebAPI : 5. 使用 POST 要求與 application/x-www-form-urlencoded 類型編碼內容 呼叫 Web API

現在,我們要進入使用 POST 要求的使用方式介紹,在上一篇 C# HttpClient WebAPI : 4. 使用 GET 要求與 QueryString 呼叫 Web API 文章中,我們看到了第一種傳遞資料到後端 Web API 伺服器的方法,那就是使用查詢字串 QueryString。不過,當我們要進行 POST 要求的時候,三種常見的 POST 提交資料的方式為:application/x-www-form-urlencoded / multipart/form-data / application/json;這些不同的方式在於要將提交的資料所進行的資料封裝過程中,使用不同的編碼技術,不論使用哪種編碼技術,這些要傳送的資料,需要放置到 Http Body 內。
在這篇文章中,我們將要使用第一種傳送資料到遠端 Web API 伺服器的技巧,那就是將要傳送的資料,使用 application/x-www-form-urlencoded 編碼技術。

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

使用 POST 要求與 application/x-www-form-urlencoded

這個編碼技術應該是在使用 POST 要求進行 Web API 呼叫的時候,最常使用到的編碼技術,一般在 HTML 表單設計的時候,如果沒有特別指定 enctype 這個屬性值,此時,就會使用 application/x-www-form-urlencoded 這樣的編碼技術來提交資料到遠端 Web 伺服器上。
要採用這樣的編碼技術來傳送資料,你需要:
  • 在 Http Header 中,設定 Content-Type 的 Header 值為 application/x-www-form-urlencoded
  • 將要傳送的資料,按照 key1=val1&key2=val2&key3=val3 的方式進行編碼,其中,key 表示這個資料欄位的名稱,val則是這個資料欄位實際的數值,而 key 和 val 都需要進行 URL 編碼。
當要進行傳送資料的 application/x-www-form-urlencoded 編碼之程式碼設計,可以參考 FormDataPostAsync 方法中的 使用 FormUrlEncodedContent 產生要 Post 的資料 region 區塊程式碼;在這個 region 程式碼區段中,我們標示了兩種做法,在 方法一: 使用字串名稱用法 中,我們使用字串的方式標示出各個資料欄位的名稱,在 方法二: 強型別用法 中,我們使用了強型別的設計技巧,也就是使用 nameof運算子取得這個欄位屬性的名稱,這樣,我們就不需要自行填入該欄位名稱字串,也就會減少了因為打錯欄位名字而造成執行時期錯的機會。
 當在進行 C# 程式設計的時候,能使用強型別的方式來進行程式設計開發,就應該採用強型別的方式,而不要採用動態或者字串的方式來撰寫這些程式碼。
我們建立一個型別 Dictionary<string, string> 的變數,formDataDictionary,並且將欄位名稱使用鍵值方式填入,而該欄位的數值,則填入該 Dictionary 的數值部分。一旦都填入完成之後,我們使用基底類別庫提供的 FormUrlEncodedContent 方法,將其轉換成為具有 application/x-www-form-urlencoded 編碼表示格式。
最後,我們就可以使用 POST 要求,將所定義的資料,使用 application/x-www-form-urlencoded 編碼方式,傳送到遠端伺服器上,我們僅需要在 PostAsync 方法的第二個引數,設定為 FormUrlEncodedContent物件,如同我們寫的程式碼: await client.PostAsync(fooFullUrl, formData)
至於其他的程式碼寫法,則是會與之前文章的用法一樣。
private static async Task<APIResult> FormDataPostAsync(APIData apiData)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost";
                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 使用 FormUrlEncodedContent 產生要 Post 的資料
                //// 方法一: 使用字串名稱用法
                //var formData = new FormUrlEncodedContent(new[] {
                //    new KeyValuePair<string, string>("Id", apiData.Id.ToString()),
                //    new KeyValuePair<string, string>("Name", apiData.Name),
                //    new KeyValuePair<string, string>("Filename", apiData.Filename)
                //});

                // 方法二: 強型別用法
                // https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/nameof
                Dictionary<string, string> formDataDictionary = new Dictionary<string, string>()
                {
                    {nameof(APIData.Id), apiData.Id.ToString() },
                    {nameof(APIData.Name), apiData.Name },
                    {nameof(APIData.Filename), apiData.Filename }
                };

                // https://msdn.microsoft.com/zh-tw/library/system.net.http.formurlencodedcontent(v=vs.110).aspx
                var formData = new FormUrlEncodedContent(formDataDictionary);
                #endregion

                response = await client.PostAsync(fooFullUrl, formData);
                #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;
}
不過,我們在前面有提到,當要進行 POST 要求呼叫的時候,需要設定 Content-Type 的需求,這些關於 Post 要求要處理地的相關動作與設定 Http 封包格式的內容,都會由 PostAsync 這個方法幫我們處理好,我們僅需要傳入該 POST 要求的 URL與FormUrlEncodedContent 物件。

觸發的 Web API 動作

這個範例中,將會指向 URL http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 APIResult FormUrlencodedPost([FromForm]APIData value) 動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost("FormUrlencodedPost")]
public APIResult FormUrlencodedPost([FromForm]APIData value)
{
    APIResult foo;

    if (value.Id == 777)
    {
        foo = new APIResult()
        {
            Success = true,
            Message = "透過 post 方法,接收到 Id=777 資料",
            Payload = value
        };
    }
    else
    {
        foo = new APIResult()
        {
            Success = false,
            Message = "無法發現到指定的 ID",
            Payload = null
        };
    }
    return foo;
}

進行測試

在程式進入點函式,我們建立一個 APIData 型別的物件,接著,設定該物件的相關屬性,這些屬性值,是我們要傳送到遠端伺服器端的資料,由上面的程式碼中,我們可以知道,當 Id 這個屬性值為 777 的時候,該 Web API 動作將會回覆通知,這次的呼叫是成功的,否則,會回覆此次 Web API 呼叫失敗。
static void Main(string[] args)
{
    var fooAPIData = new APIData()
    {
        Id = 777,
        Name = "VulcanSource",
        Filename = "",
    };
    var foo = FormDataPostAsync(fooAPIData).Result;
    Console.WriteLine($"使用 form-urlencoded 格式與使用 Post 方法呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"Payload : {foo.Payload}");
    Console.WriteLine($"");

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

    fooAPIData = new APIData()
    {
        Id = 123,
        Name = "VulcanSource",
        Filename = "",
    };
    foo = FormDataPostAsync(fooAPIData).Result;
    Console.WriteLine($"使用 form-urlencoded 格式與使用 Post 方法呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"Payload : {foo.Payload}");
    Console.WriteLine($"");

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

執行結果

這個測試將會輸出底下內容
使用 form-urlencoded 格式與使用 Post 方法呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 透過 post 方法,接收到 Id=777 資料
Payload : {
  "id": 777,
  "name": "VulcanSource",
  "filename": null
}

Press any key to Exist...

使用 form-urlencoded 格式與使用 Post 方法呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 無法發現到指定的 ID
Payload :

Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    在這裡的第三行中,您將會看到一個名為 Content-Type 的 Header,其值為 application/x-www-form-urlencoded,這表示了,當把這個 Http 封包送達 Web API 伺服器的時候,也通知該伺服器,這個 Http 封包的 Body 部分,是使用 application/x-www-form-urlencoded MIME 格式進行封裝的;會有這樣的結果產生,那是因為,我們有產生一個 new FormUrlEncodedContent(formDataDictionary) 這樣的物件,並且使用 PostAsync 方法,將這個物件傳送到這個非同步方法內。
    在 Http 標準規範中有提到,當要進行 Post 動作要求的時候,需要使用 Content-Length Htpp Header,說明該 Http Body 的實際資料長度是多少?
    在最後一行中,我們看到了 Id=777&Name=VulcanSource&Filename= ,這部分的內容是屬於 Http 請求封包中的 Body 部分,也就是我們實際要送出的資料內容,這些資料是採用 Form Url Encoder 的方式來進行編碼的,此時的 Id值為 777。
    以上的部分我們並沒需要自己透過程式來計算與指定這些內容的實際數值。
POST http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Host: vulcanwebapi.azurewebsites.net
Content-Length: 34
Expect: 100-continue
Connection: Keep-Alive

Id=777&Name=VulcanSource&Filename=
  • 反應 (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: Thu, 19 Oct 2017 02:06:45 GMT

84
{"success":true,"message":"透過 post 方法,接收到 Id=777 資料","payload":{"id":777,"name":"VulcanSource","filename":null}}
0
  • 請求 (Request)
    在這裡的第三行中,您將會看到一個名為 Content-Type 的 Header,其值為 application/x-www-form-urlencoded,這表示了,當把這個 Http 封包送達 Web API 伺服器的時候,也通知該伺服器,這個 Http 封包的 Body 部分,是使用 application/x-www-form-urlencoded MIME 格式進行封裝的;會有這樣的結果產生,那是因為,我們有產生一個 new FormUrlEncodedContent(formDataDictionary) 這樣的物件,並且使用 PostAsync 方法,將這個物件傳送到這個非同步方法內。
    在 Http 標準規範中有提到,當要進行 Post 動作要求的時候,需要使用 Content-Length Htpp Header,說明該 Http Body 的實際資料長度是多少?
    在最後一行中,我們看到了 Id=123&Name=VulcanSource&Filename= ,這部分的內容是屬於 Http 請求封包中的 Body 部分,也就是我們實際要送出的資料內容,這些資料是採用 Form Url Encoder 的方式來進行編碼的,此時的 Id值為 123。
    以上的部分我們並沒需要自己透過程式來計算與指定這些內容的實際數值。
POST http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Host: vulcanwebapi.azurewebsites.net
Content-Length: 34
Expect: 100-continue

Id=123&Name=VulcanSource&Filename=
  • 反應 (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: Thu, 19 Oct 2017 02:06:49 GMT

48
{"success":false,"message":"無法發現到指定的 ID","payload":null}
0

相關文章索引

C# HttpClient WebAPI 系列文章索引



對於已經具備擁有 .NET / C# 開發技能的開發者,可以使用 Xamarin.Forms Toolkit 開發工具,便可以立即開發出可以在 Android / iOS 平台上執行的 App;對於要學習如何使用 Xamarin.Forms & XAML 技能,現在已經推出兩本電子書來幫助大家學這這個開發技術。
這兩本電子書內包含了豐富的逐步開發教學內容與相關觀念、各種練習範例,歡迎各位購買。
Xamarin.Forms 電子書
想要購買 Xamarin.Forms 快速上手 電子書,請點選 這裡

想要購買 XAML in Xamarin.Forms 基礎篇 電子書,請點選 這裡





沒有留言:

張貼留言