針對 .NET / CLR / C# / Blazor / MAUI / Xamarin / .NET Core / .NET Framework / OOP / Design Pattern 等相關程式開發議題進行研究與寫成相關心得筆記。
2017年10月17日 星期二
2017年10月16日 星期一
C# HttpClient WebAPI : 2. 最簡單的方式使用 HttpClient 類別
若你正在觀看此篇文章,則應該會對於 使用 HttpClient 進行 JWT 身分驗證與呼叫需要授權的 API 和重新更新 Token 權杖的程式設計範例 這篇文章更感興趣。
在這裡,我們將會說明如何非常簡單的來使用 HttpClient 類別所產生的物件,進行呼叫遠端的 Web API,在這裡,我們將需要呼叫
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
在這裡,我們將會說明如何非常簡單的來使用 HttpClient 類別所產生的物件,進行呼叫遠端的 Web API,在這裡,我們將需要呼叫
http://vulcanwebapi.azurewebsites.net/api/values/777
這個 URL,取得回傳的 JSON 文字內容。
首先,您需要產生一個 HttpClient 類別的物件,client,接著,就可以使用執行個體方法
GetStringAsync
這個非同步方法,將上述的 URL 當作引述傳送過去,就可以取得這個 URL 執行完畢的執行結果,JSON 文字內容,然後,我們就把執行結果輸出到螢幕上。
你可以從專案
SimpleHttpClient
中看到如下的測試程式碼。static async Task Main(string[] args)
{
HttpClient client = new HttpClient();
var fooResult = await client.GetStringAsync("http://vulcanwebapi.azurewebsites.net/api/values/777");
Console.WriteLine($"{fooResult}");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
上面的程式碼執行結果如下所示,因為我們使用的是
GetStringAsync
這個非同步方法,使用 Get 動作來呼叫遠端 Web API;另外,從方法名稱你就可以看的出來,我們這裡得到的執行結果內容,將會是字串。{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan01","filename":null}}
Press any key to Exist...
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)
GET http://vulcanwebapi.azurewebsites.net/api/values/777 HTTP/1.1
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
- 反應 (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: Wed, 18 Oct 2017 15:46:58 GMT
78
{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan01","filename":null}}
0
HttpClient 類別,提供了很多 Get 動作呼叫的方法,這些方法與相關的多載方法定義,將為回傳不同 Web API 執行結果的內容。
- GetAsync(String)以非同步作業的方式,將 GET 要求傳送至指定的 URI。
- GetByteArrayAsync(String)將 GET 要求傳送至指定的 URI,並透過非同步作業,以位元組陣列形式傳回回應內容。
- GetStreamAsync(String)將 GET 要求傳送至指定的 URI,並透過非同步作業,以資料流形式傳回回應內容。
現在,我們替換 URL 成為一個不存在的 Web API URL
http://vulcanwebapi.azurewebsites.net/api/XXX/777
讓我們來看看會有甚麼結果,底下是我們要測試的程式碼
這裡的專案原始碼,可以從BadUrl
專案中找到
HttpClient client = new HttpClient();
var fooResult = await client.GetStringAsync("http://vulcanwebapi.azurewebsites.net/api/XXX/777");
Console.WriteLine($"{fooResult}");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
此時,Visual Studio 將會出現這樣的畫面與文字內容
未處理的例外狀況:System.Net.Http.HttpRequestException: '回應狀態碼未表示成功: 404 (Not Found)。'
很明顯的,若當我們呼叫 HttpClient.GetStringAsync 方法,所提供的 URL 是個不正確的 URL,此時,HttpClient 類別物件就會發出例外異常的訊息出來,並且,您的程式也就中斷了。
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)
GET http://vulcanwebapi.azurewebsites.net/api/XXX/777 HTTP/1.1
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
- 反應 (Response)
HTTP/1.1 404 Not Found
Content-Length: 0
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Wed, 18 Oct 2017 15:51:03 GMT
那麼,該要如何處理這樣問題呢?接下來的文章中,將會有更多這方面的程式開發建議。
最後,讓我們來看看這個遠端 Web API 的程式碼:
[HttpGet("{id}")]
public APIResult Get(int id)
{
APIResult foo;
if (id == 777)
{
foo = new APIResult()
{
Success = true,
Message = "透過 Get 方法,接收到 Id=777",
Payload = new APIData()
{
Id = 777,
Name = "Vulcan01"
}
};
}
else
{
foo = new APIResult()
{
Success = false,
Message = "無法發現到指定的 ID",
Payload = null
};
}
return foo;
}
在這個測試範例中,將會執行上述的要求動作方法,從這個方法宣告中,我們可以看到,將會要得到一個 id 參數,代表要查看的物件代碼編號,接著,在程式碼中,將會檢查這個 id 是否為 777,並且根據檢查結果,回傳不同的 APIResult 物件。
在這個
Get(int id)
方法中,我們看到他的回傳型別為 APIResult,而在 ASP.NET Core Web API 系統中,預設將回傳的物件,序列化成為 JSON 格式,所以,在用戶端的程式碼中,你將會得到類似這樣的文字字串。{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan01","filename":null}}
相關文章索引
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 課程
訂閱:
文章 (Atom)
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 在台灣的學習技術資源