2017年10月24日 星期二

C# HttpClient WebAPI : 10. 使用 POST 要求與使用 Header 進行傳送與接收 呼叫 Web API

若你正在觀看此篇文章,則應該會對於 使用 HttpClient 進行 JWT 身分驗證與呼叫需要授權的 API 和重新更新 Token 權杖的程式設計範例 這篇文章更感興趣。

通常來說,Http 通訊協議是無狀態協議 (Stateless Protocol),也就是說,每次呼叫 Web API 的時候,若有些持續性要保留下來的狀態,我們就需要自己將這些狀態保留在用戶端中;而當下次要進行新的 Web API 呼叫的時候,就需要把這些狀態內容,再度傳遞後端的 Web API 伺服器上,這樣,後端 Web API 的控制器,就會知道如何繼續進行該狀態下的操作。
例如,我們需要透過 Web API 來進行身分驗證,當驗證成功之後,會收到一個 Token 值,代表此次通過身分驗證的這個使用者;下次,我們需要以此使用者身分,進行資料的 CRUD 操作(這些操作,需要提供一個 Token,代表已經經過 Web API 伺服器的身分驗證),這樣,伺服器就可以針對這個使用者所允許的權限下,進行相關的資料處理。
在這裡,我們不會把這樣的複雜的應用環境帶到這個練習中,我們將僅會針對如何透過 Http Header,將這些用法作介紹

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

使用 POST 要求與使用 Header 進行傳送與接收 呼叫 Web API

在這個 JsonPostAsync 方法中,第二個參數為一個布林型別,表示此次呼叫 Web API 的動作,是否需要加自行定義的 Header 資訊,傳送到後端 Web API 伺服器內。若需要傳遞自行定義的 Header,在這裡使用了 client.DefaultRequestHeaders.Add("VerifyCode", loginInformation.VerifyCode); 表示式,將該 Header 的欄位名稱與欄位數值,加入到 DefaultRequestHeaders 集合物件內。
接著,其他的程式碼寫法。都與一般的 POST 要求呼叫使用方式相同,在這個範例中,我們所傳送到 Web API 伺服器上的資料,使用的是 JSON 編碼方式。
private static async Task<APIResult> JsonPostAsync(LoginInformation loginInformation,
    bool sendHeader)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                //string FooUrl = $"http://localhost:53495/api/values/HeaderPost";
                string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Values/HeaderPost";
                HttpResponseMessage response = null;

                #region  設定相關網址內容
                var fooFullUrl = $"{FooUrl}";
                if (sendHeader == true)
                {
                    client.DefaultRequestHeaders.Add("VerifyCode", loginInformation.VerifyCode);
                }

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

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

                var fooJSON = JsonConvert.SerializeObject(loginInformation);
                using (var fooContent = new StringContent(fooJSON, Encoding.UTF8, "application/json"))
                {
                    response = await client.PostAsync(fooFullUrl, fooContent);
                }
                #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/values/HeaderPost ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult HeaderGet([FromBody]LoginInformation loginInformation) 動作(Action),其該動作的原始碼如下所示。
在這個動作(Action)方法中,我們使用了 this.HttpContext.Request.Headers.TryGetValue("VerifyCode", out VerifyCode); 表示式,嘗試取得這次 Http 要求中的 Header 資訊,我們需要取得 Header 中關於 VerifyCode 的欄位數值。若該 Header 不存在,也就是用戶端並沒有傳送這個 Header 到伺服器上,此時將會回報 驗證碼沒有發現 的不成功存取訊息。若可以成功取得這個 Header 的欄位數值,我們將會檢查該 Header 欄位值是否為 123,若不是這個值,則一樣視為不成功存取動作,並且會回報 驗證碼不正確 錯誤訊息。最後,若 VerifyCode 的欄位數值驗證成功,接這就需要驗證帳號與密碼是否正確。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost("HeaderPost")]
public APIResult HeaderGet([FromBody]LoginInformation loginInformation)
{
    APIResult foo;
    StringValues VerifyCode = "";

    this.HttpContext.Request.Headers.TryGetValue("VerifyCode", out VerifyCode);
    if (StringValues.IsNullOrEmpty(VerifyCode))
    {
        foo = new APIResult()
        {
            Success = false,
            Message = "驗證碼沒有發現",
            Payload = null
        };
    }
    else
    {
        if (VerifyCode != "123")
        {
            foo = new APIResult()
            {
                Success = false,
                Message = "驗證碼不正確",
                Payload = null
            };
        }
        else
        {
            if (loginInformation.Account == "Vulcan" &&
                loginInformation.Password == "123")
            {
                foo = new APIResult()
                {
                    Success = true,
                    Message = "這個帳號與密碼正確無誤",
                    Payload = null
                };
            }
            else
            {
                foo = new APIResult()
                {
                    Success = false,
                    Message = "這個帳號與密碼不正確",
                    Payload = null
                };
            }
        }
    }
    return foo;
}

進行測試

在程式進入點函式,我們建立一個 LoginInformation 型別的物件,接著,設定該物件的相關屬性,這些屬性值,是我們要傳送到遠端伺服器端的資料。
在這裡,我們一共呼叫了四次該 Web API:
  • 第1次
    這裡有傳遞這個 VerifyCode Header 欄位,並且該 VerifyCode 欄位值是伺服器所需要的正確值,另外,在 Http Body 部分,我們提供了一個帳號與密碼為正確的 JSON 編碼資料。所以,會得到 這個帳號與密碼正確無誤 這樣的訊息。
  • 第2次
    這裡有傳遞這個 VerifyCode Header 欄位,並且該 VerifyCode 欄位值是伺服器所需要的正確值,另外,在 Http Body 部分,我們提供了一個不正確帳號密碼之物件的 JSON 編碼資料。所以,會得到 這個帳號與密碼不正確 這樣的訊息。
  • 第3次
    這裡有傳遞這個 VerifyCode Header 欄位,並且該 VerifyCode 欄位值設定成為不是伺服器所需要的正確值,因此,當後端的 Web API 控制器的相對應動作(Action)方法執行後,將會回傳 驗證碼不正確這樣的訊息。
  • 第4次
    這裡並沒有傳遞任何 VerifyCode Header 欄位到遠端 Web API 伺服器上,不過,後端的 Web API 控制器的相對應動作(Action)方法一樣會被執行,並經過檢查後,發現到此一問題,就會回傳 驗證碼沒有發現 這樣的訊息。
static void Main(string[] args)
{
    var fooLoginInformation = new LoginInformation()
    {
        Account = "Vulcan",
        Password = "123",
        VerifyCode = "123"
    };
    var foo = JsonPostAsync(fooLoginInformation, true).Result;
    Console.WriteLine($"使用 Post 方法,搭配 JSON 與 Header,呼叫 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();

    fooLoginInformation = new LoginInformation()
    {
        Account = "Vuln",
        Password = "13",
        VerifyCode = "123"
    };
    foo = JsonPostAsync(fooLoginInformation, true).Result;
    Console.WriteLine($"使用 Post 方法,搭配 JSON 與 Header,呼叫 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();

    fooLoginInformation = new LoginInformation()
    {
        Account = "Vulcan",
        Password = "123",
        VerifyCode = "888"
    };
    foo = JsonPostAsync(fooLoginInformation, true).Result;
    Console.WriteLine($"使用 Post 方法,搭配 JSON 與 Header,呼叫 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();

    fooLoginInformation = new LoginInformation()
    {
        Account = "Vulcan",
        Password = "123",
        VerifyCode = "888"
    };
    foo = JsonPostAsync(fooLoginInformation, false).Result;
    Console.WriteLine($"使用 Post 方法,搭配 JSON 與 Header,呼叫 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();

}

執行結果

這個測試將會輸出底下內容
使用 Post 方法,搭配 JSON 與 Header,呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 這個帳號與密碼正確無誤
Payload :

Press any key to Exist...

使用 Post 方法,搭配 JSON 與 Header,呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 這個帳號與密碼不正確
Payload :

Press any key to Exist...

使用 Post 方法,搭配 JSON 與 Header,呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 驗證碼不正確
Payload :

Press any key to Exist...

使用 Post 方法,搭配 JSON 與 Header,呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 驗證碼沒有發現
Payload :

Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    這裡的 POST 要求,有傳送 VerifyCode Header 欄位,所以,我們從底下第二行中,看到了這樣的內容 VerifyCode: 123,這個 VerifyCode Header 欄位值為 123
POST http://vulcanwebapi.azurewebsites.net/api/Values/HeaderPost HTTP/1.1
VerifyCode: 123
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: vulcanwebapi.azurewebsites.net
Content-Length: 56
Expect: 100-continue
Connection: Keep-Alive

{"Account":"Vulcan","Password":"123","VerifyCode":"123"}
  • 反應 (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: Sun, 22 Oct 2017 08:44:58 GMT

4d
{"success":true,"message":"這個帳號與密碼正確無誤","payload":null}
0
  • 請求 (Request)
    這裡的 POST 要求,有傳送 VerifyCode Header 欄位,所以,我們從底下第二行中,看到了這樣的內容 VerifyCode: 123,這個 VerifyCode Header 欄位值為 123
POST http://vulcanwebapi.azurewebsites.net/api/Values/HeaderPost HTTP/1.1
VerifyCode: 123
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: vulcanwebapi.azurewebsites.net
Content-Length: 53
Expect: 100-continue

{"Account":"Vuln","Password":"13","VerifyCode":"123"}
  • 反應 (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: Sun, 22 Oct 2017 08:45:00 GMT

4b
{"success":false,"message":"這個帳號與密碼不正確","payload":null}
0
  • 請求 (Request)
    這裡的 POST 要求,有傳送 VerifyCode Header 欄位,所以,我們從底下第二行中,看到了這樣的內容 VerifyCode: 888,這個 VerifyCode Header 欄位值為 888,這個 VerifyCode Header 欄位值並不會被後端 Web API 伺服器所接受。
POST http://vulcanwebapi.azurewebsites.net/api/Values/HeaderPost HTTP/1.1
VerifyCode: 888
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: vulcanwebapi.azurewebsites.net
Content-Length: 56
Expect: 100-continue

{"Account":"Vulcan","Password":"123","VerifyCode":"888"}
  • 反應 (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: Sun, 22 Oct 2017 08:45:08 GMT

3f
{"success":false,"message":"驗證碼不正確","payload":null}
0
  • 請求 (Request)
    這裡的 POST 要求,並沒有傳送 VerifyCode Header 欄位,所以,我們無法從底的 Http 請求封包中,看到任何這方面的資訊;而因為沒有。VerifyCode Header 欄位,將會造成後端 Web API回報錯誤訊息到用戶端。
POST http://vulcanwebapi.azurewebsites.net/api/Values/HeaderPost HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: vulcanwebapi.azurewebsites.net
Content-Length: 56
Expect: 100-continue

{"Account":"Vulcan","Password":"123","VerifyCode":"888"}
  • 反應 (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: Sun, 22 Oct 2017 08:45:09 GMT

42
{"success":false,"message":"驗證碼沒有發現","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 基礎篇 電子書,請點選 這裡



沒有留言:

張貼留言