若你正在觀看此篇文章,則應該會對於 使用 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