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