在這個範例程式碼中,我們宣告了一個非同步方法,他的方法簽章為 async Task<APIResult> HttpGetAsync(),在這個方法內,將會使用 HttpClient.GetAsync 方法,來進行呼叫遠端 Web API 的 GET 要求動作;當我們自行設計的方法,屬於非同步的方法,請記得要在這個方法名稱之後,加入 Async 名稱,例如:HttpGetAsync,這樣,只要您一看到這樣的名稱,就知道他是個非同步的方法,並且可以使用 await 關鍵字來進行非同步方式的呼叫。
在我們所做的一系列練習中,後端的 Web API 伺服器在接受用戶端(Client)的請求之後,會將要回報的結果,包裝在 APIResult 類別物件中,其中,在這個類別的屬性 Success 會標示這次呼叫 Web API 處理結果是否成功;而屬性 Message,則會標註這次呼叫 Web API 結果的說明文字內容;最後,若是這次呼叫 Web API,需要得到一些資料,則會將要回報的資料存放在 Payload 這個屬性中,不過,原則上,在 Payload 這個屬性中,將會儲存要回傳物件的 JSON 內容,因此,我們僅需要將這些字串內容,使用 JSON 反序列化的動作,就可以取得回報的結果物件。
使用 C# 7.1 的新功能
在以往的主控台應用程式專案中的程式進入點 Main 方法,只能夠是 void 或者 int 型別,這樣會造成我們無法在 Main 方法內,使用非同步 await 關鍵字的方式來呼叫,不過,從 C# 7.1 開始,Main 的傳回型別可以是 void、int,或是 Task、Task\。
可是,當你建立一個主控台應用專案之後,發現到你無法使用這樣的進入點函式宣告 static async Task Main(string[] args),因為,若你這樣宣告 Main 方法,就會產生編譯時期的錯誤。
我們在用戶端進行 Web API 呼叫的時候,會用到許多類別物件,例如:HttpClientHandler / HttpClient,這些類別都有實作出 IDisposable 介面,為了要能夠讓在這些類別所生成的物件,可以加速釋放掉非受管理的記憶體,我們需要使用 using 陳述式,否則,您就需要自行呼叫這些物件的 Dispose()方法。
HttpClient 會使用 HttpMessageHandler 管道(pipeline) 來傳送與接收相關請求,而 HttpClientHandler 就是 HttpClient 用於 HttpMessageHandler 管道(pipeline)的預設訊息處理常式,關於更詳盡的運作原理,可以參考 HTTP Message Handlers in ASP.NET Web API
這個類別提供了這些執行緒安全的方法,讓我們可以存取遠端的 Web API 服務:DeleteAsync、GetAsync、GetByteArrayAsync、GetStreamAsync、GetStringAsync、PostAsync、PutAsync、SendAsync
使用例外異常捕捉任何異常錯誤
在進行電腦程式設計的時候,務必要記得,當要進行資源(記憶體、檔案、網路等等)存取的時候,一定要將這些存取敘述放在 try { } 區塊內;例如,若網路突然斷線,造成無法繼續存取遠端的 Web API 服務,此時,你的程式將會發出例外異常,若您沒有做任何處置,則將會造成您的程式異常中斷,無法繼續執行下去。
在這裡,只要使用 HttpClient 物件進行後端 Web API 呼叫過程中,發生了任何例外異常狀況,我們將會在 catch 區塊內,產生一個 APIResult 物件,並且設定此次 Web API 呼叫為失敗的,並且將例外異常訊息放到 APIResult.Message 屬性上。透過這樣的設計,只要 APIResult.Success 不為 true,那叫表示呼叫 Web API 過程失敗或者不成功,可以將 APIResult.Message 顯示給使用者,讓他們知道發生了甚麼問題;最重要的是,您的應用程式不會異常終止。
我們繼續往這個程式碼看下去,現在已經使用 using 陳述式,建立了 HttpClientHandler / HttpClient 的物件,現在,我們就可以使用 HttpClient 的物件,進行 Web API 需求處理。
通常,我們需要告訴後端 Web API 伺服器,告知需要將結果使用甚麼樣的 MIME 格式回傳到用戶端中,在這裡,我們需要將這樣的需求,使用名稱為 Accept 的 Header,填入 Http 請求的 Header 中;在底下的程式碼中,我們將會告知 Web API 伺服器,期望使用結果為 JSON 格式回傳到呼叫的用戶端中。
這個範例中,將會指向 URL http://vulcanwebapi.azurewebsites.net/api/values ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult Get() 動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的集合 JSON 資料。
[HttpGet]
public APIResult Get()
{
APIResult foo = new APIResult()
{
Success = true,
Message = "成功得所有資料集合",
Payload = new List<APIData>()
{
new APIData()
{
Id =777,
Name = "Vulcan01"
},
new APIData()
{
Id =234,
Name ="Vulcan02"
}
}
};
return foo;
}
進行測試
在程式進入點函式,我們呼叫了非同步方法 HttpGetAsync(),這個方法會使用 HttpClient 物件,使用 GET 要求,與使用 http://vulcanwebapi.azurewebsites.net/api/values 這個 URL,進行後端 Web API 的呼叫。(你也可以使用瀏覽器,輸入這個網址 http://vulcanwebapi.azurewebsites.net/api/values ,也可看到 Web API 的回傳 JSON 文字內容)
不論此次 Web API 呼叫是否有成功,都會得到 APIResult 類別物件,我們可以從這個物件,得到更加詳細的 Web API 回傳結果;在這個範例中,將會得到一個集合 List<APIData> ,所以,我們從 APIResult.Payload 屬性中,將其反序列化回來,並且逐一將 APIData 物件值顯示在螢幕上。
staticasync Task Main(string[] args)
{
var foo = await HttpGetAsync();
Console.WriteLine($"使用 Get 方法呼叫 Web API 的結果");
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
var fooAPIData = JsonConvert.DeserializeObject<List<APIData>>(foo.Payload.ToString());
foreach (var item in fooAPIData)
{
Console.WriteLine($"Id : {item.Id}");
Console.WriteLine($"Name : {item.Name}");
Console.WriteLine($"Filename : {item.Filename}");
}
Console.WriteLine($"");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
執行結果
這個測試將會輸出底下內容
使用 Get 方法呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 成功得所有資料集合
Id : 777
Name : Vulcan01
Filename :
Id : 234
Name : Vulcan02
Filename :
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
HttpClient client = new HttpClient();敘述,建立一個HttpClient類別的執行個體,接著就進行處理 GET 要求動作;這樣的過程看是很簡單與容易,可是對於我們實際進行專案開發上,會存在這許多問題需要克服,例如:要使用await的非同步方式來呼叫client.GetStringAsync方法,還是要使用同步的方式,client.GetStringAsync("...URL...").Result,來啟動 Http 各種要求的動作;另外,對於得到的 Web API 呼叫結果,我們如何進行操作,是要使用動態解析的方式來處理,還是可以選擇較好用的強型別方式、對於 JSON 的序列與反序列化操作,又該如何設計這方面的程式碼呢?使用 GET 要求傳送至指定的 URI
private static async Task<APIResult> HttpGetAsync() { 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"; 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"); response = await client.GetAsync(fooFullUrl); #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; }採用非同步方法
Task或Task<T>,千萬不要使用void。關於這方面的詳細說明,可以參考 C# 非同步程式設計的 async void 與 async Task 的差異void的回傳值。async Task<APIResult> HttpGetAsync(),在這個方法內,將會使用HttpClient.GetAsync方法,來進行呼叫遠端 Web API 的 GET 要求動作;當我們自行設計的方法,屬於非同步的方法,請記得要在這個方法名稱之後,加入Async名稱,例如:HttpGetAsync,這樣,只要您一看到這樣的名稱,就知道他是個非同步的方法,並且可以使用await關鍵字來進行非同步方式的呼叫。Async作為該函式名稱的結尾。Task<APIResult>,也就是,在我們設計的方法中,需要回傳一個類別為APIResult類別產生的物件,底下是這個類別的定義。/// <summary> /// 呼叫 API 回傳的制式格式 /// </summary> public class APIResult { /// <summary> /// 此次呼叫 API 是否成功 /// </summary> public bool Success { get; set; } = true; /// <summary> /// 呼叫 API 失敗的錯誤訊息 /// </summary> public string Message { get; set; } = ""; /// <summary> /// 呼叫此API所得到的其他內容 /// </summary> public object Payload { get; set; } }APIResult類別物件中,其中,在這個類別的屬性Success會標示這次呼叫 Web API 處理結果是否成功;而屬性Message,則會標註這次呼叫 Web API 結果的說明文字內容;最後,若是這次呼叫 Web API,需要得到一些資料,則會將要回報的資料存放在Payload這個屬性中,不過,原則上,在Payload這個屬性中,將會儲存要回傳物件的 JSON 內容,因此,我們僅需要將這些字串內容,使用 JSON 反序列化的動作,就可以取得回報的結果物件。使用 C# 7.1 的新功能
static async Task Main(string[] args),因為,若你這樣宣告 Main 方法,就會產生編譯時期的錯誤。Properties這個節點建置標籤頁次進階按鈕進階建置設定對話窗中,點選語言版本這個下拉選單項目,選擇C# 7.1項目,此時,您就會看到剛剛的錯誤訊息消失了。充分應用 using 陳述式
HttpClientHandler/HttpClient,這些類別都有實作出IDisposable介面,為了要能夠讓在這些類別所生成的物件,可以加速釋放掉非受管理的記憶體,我們需要使用using陳述式,否則,您就需要自行呼叫這些物件的Dispose()方法。HttpClientHandler/HttpClient物件,並且搭配using陳述式的程式碼用法。using (HttpClientHandler handler = new HttpClientHandler()) { using (HttpClient client = new HttpClient(handler)) { ... } }存取 Web API 用的類別:HttpClientHandler / HttpClient
HttpClientHandler/HttpClientHttpClientHandler就是 HttpClient 用於 HttpMessageHandler 管道(pipeline)的預設訊息處理常式,關於更詳盡的運作原理,可以參考 HTTP Message Handlers in ASP.NET Web API使用例外異常捕捉任何異常錯誤
APIResult物件,並且設定此次 Web API 呼叫為失敗的,並且將例外異常訊息放到 APIResult.Message 屬性上。透過這樣的設計,只要 APIResult.Success 不為 true,那叫表示呼叫 Web API 過程失敗或者不成功,可以將 APIResult.Message 顯示給使用者,讓他們知道發生了甚麼問題;最重要的是,您的應用程式不會異常終止。catch (Exception ex) { fooAPIResult = new APIResult { Success = false, Message = ex.Message, Payload = ex, }; }呼叫 Web API 前置處理動作
HttpClientHandler/HttpClient的物件,現在,我們就可以使用 HttpClient 的物件,進行 Web API 需求處理。Accept的 Header,填入 Http 請求的 Header 中;在底下的程式碼中,我們將會告知 Web API 伺服器,期望使用結果為 JSON 格式回傳到呼叫的用戶端中。client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");,被註解起來,這個Content-TypeHttp Header,是用來告知遠端的 Web API 伺服器,此次傳送過去的 Http Body,其 MIME 的型別是甚麼?這個 C# 敘述,特別適合當您需要使用 JSON 格式文字,傳送到遠端 Web API 伺服器中,這樣,Web API 伺服器將會知道您送過來的資料,是屬於 JSON 的MIME 格式。進行 Web API 要求動作
HttpResponseMessage類別的物件,我們將會透過這個物件,確認 Web API 執行結果是否正確與取得伺服器回傳的 JSON 內容。response = await client.GetAsync(fooFullUrl);response.Content.Headers.FirstOrDefault(x => x.Key == "Content-Type").Value.FirstOrDefault();,就可以得到了;在下圖,是我們在除錯模式下,設定的執行中斷的情況。檢查 Web API 回傳結果
IsSuccessStatusCode確認 Http 狀態碼是否為成功。await response.Content.ReadAsStringAsync();非同步呼叫敘述,將伺服器回傳的 JSON 字串取得,接著,透過 JSON.NET 套件,使用強型別的JsonConvert.DeserializeObject<APIResult>泛型方法,把 JSON 字串,反序列化成為 C# 的物件,也就是APIResult類別的物件。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, }; }觸發的 Web API 動作
http://vulcanwebapi.azurewebsites.net/api/values,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的public APIResult Get()動作(Action),其該動作的原始碼如下所示。[HttpGet] public APIResult Get() { APIResult foo = new APIResult() { Success = true, Message = "成功得所有資料集合", Payload = new List<APIData>() { new APIData() { Id =777, Name = "Vulcan01" }, new APIData() { Id =234, Name ="Vulcan02" } } }; return foo; }進行測試
HttpGetAsync(),這個方法會使用 HttpClient 物件,使用 GET 要求,與使用http://vulcanwebapi.azurewebsites.net/api/values這個 URL,進行後端 Web API 的呼叫。(你也可以使用瀏覽器,輸入這個網址 http://vulcanwebapi.azurewebsites.net/api/values ,也可看到 Web API 的回傳 JSON 文字內容)APIResult類別物件,我們可以從這個物件,得到更加詳細的 Web API 回傳結果;在這個範例中,將會得到一個集合List<APIData>,所以,我們從 APIResult.Payload 屬性中,將其反序列化回來,並且逐一將 APIData 物件值顯示在螢幕上。static async Task Main(string[] args) { var foo = await HttpGetAsync(); Console.WriteLine($"使用 Get 方法呼叫 Web API 的結果"); Console.WriteLine($"結果狀態 : {foo.Success}"); Console.WriteLine($"結果訊息 : {foo.Message}"); var fooAPIData = JsonConvert.DeserializeObject<List<APIData>>(foo.Payload.ToString()); foreach (var item in fooAPIData) { Console.WriteLine($"Id : {item.Id}"); Console.WriteLine($"Name : {item.Name}"); Console.WriteLine($"Filename : {item.Filename}"); } Console.WriteLine($""); Console.WriteLine($"Press any key to Exist...{Environment.NewLine}"); Console.ReadKey(); }執行結果
HTTP 傳送與接收原始封包
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));所產生的 Http Header,有出現在傳送的 Http 封包內容中。application/json; charset=utf-8的格式內容,也就是 JSON 內容。相關文章索引
關於 Xamarin 在台灣的學習技術資源