在這個範例程式碼中,我們宣告了一個非同步方法,他的方法簽章為 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
/HttpClient
HttpClientHandler
就是 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-Type
Http 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 在台灣的學習技術資源