本教學課程會建置在 GitHub 上對 REST 服務發出 HTTP 要求的應用程式。 應用程式會以 JSON 格式讀取資訊,並將 JSON 轉換成 C# 物件。 從 JSON 轉換成 C# 物件稱為 還原串行化。
本教學課程示範如何:
- 傳送 HTTP 要求。
- 解序列化 JSON 回應。
- 使用屬性設定反序列化。
如果您想要遵循本教學課程的 最終範例,您可以下載。 如需下載指示,請參閱 範例和教學課程。
先決條件
- 最新 .NET SDK
- Visual Studio Code 編輯器
- C# DevKit
建立用戶端應用程式
開啟命令提示字元,併為您的應用程式建立新的目錄。 讓該目錄成為目前的目錄。
在主控台視窗中輸入下列命令:
dotnet new console --name WebAPIClient此命令會建立基本 「Hello World」 應用程式的入門檔案。 專案名稱為 「WebAPIClient」。。
流覽至 「WebAPIClient」 目錄,然後執行應用程式。
cd WebAPIClientdotnet rundotnet run會自動執行dotnet restore,以還原應用程式所需的任何相依性。 如有需要,它也會執行dotnet build。 您應該會看到應用程式的輸出結果為"Hello, World!"。 在您的終端機中,按 ctrl+C 以停止應用程式。
發出 HTTP 要求
此應用程式會呼叫 GitHub API,以取得 .NET Foundation 傘下專案的相關信息。 該端點為 https://api.github.com/orgs/dotnet/repos。 若要擷取資訊,它會提出 HTTP GET 要求。 瀏覽器也會提出 HTTP GET 要求,因此您可以將該 URL 貼到瀏覽器網址列中,以查看您將接收和處理哪些資訊。
使用 HttpClient 類別提出 HTTP 要求。 HttpClient 只支援其長時間執行的 API 的異步方法。 因此,以下步驟將建立異步方法,並從主程式方法呼叫它。
在您的專案目錄中開啟
Program.cs檔案,並以下列內容取代其內容:await ProcessRepositoriesAsync(); static async Task ProcessRepositoriesAsync(HttpClient client) { }此程式碼:
- 將
Console.WriteLine語句替換為呼叫ProcessRepositoriesAsync並使用await關鍵詞。 - 定義空的
ProcessRepositoriesAsync方法。
- 將
在
Program類別中,使用 HttpClient,透過以下的 C# 程式碼來取代內容,以此來處理要求和回應。using System.Net.Http.Headers; using HttpClient client = new(); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json")); client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter"); await ProcessRepositoriesAsync(client); static async Task ProcessRepositoriesAsync(HttpClient client) { }此程式碼:
- 設置所有請求的 HTTP 標頭:
- 接受 JSON 回應的
Accept標頭 -
User-Agent標頭。 這些標頭會被 GitHub 伺服器程式代碼檢查,為了從 GitHub 擷取資訊是必要的。
- 接受 JSON 回應的
- 設置所有請求的 HTTP 標頭:
在
ProcessRepositoriesAsync方法中,呼叫 GitHub 端點,以傳回 .NET foundation 組織下所有存放庫的清單:static async Task ProcessRepositoriesAsync(HttpClient client) { var json = await client.GetStringAsync( "https://api.github.com/orgs/dotnet/repos"); Console.Write(json); }此程式碼:
- 等候從呼叫 HttpClient.GetStringAsync(String) 方法傳回的任務。 這個方法會將 HTTP GET 要求傳送至指定的 URI。 回應內容會以 String傳回,當工作完成後即可使用。
- 回應字串
json會列印至主控台。
建置應用程式並加以執行。
dotnet run沒有建置警告,因為
ProcessRepositoriesAsync現在包含await運算符。 輸出是一段冗長的 JSON 文字。
反序列化 JSON 結果
下列步驟可簡化擷取資料及處理資料的方法。 您將使用 GetFromJsonAsyncSystem.Net.Http.Json NuGet 套件一📦部分的延伸模組方法,將 JSON 結果擷取並還原序列化為物件。
建立名為 Repository.cs 的檔案,並新增下列程序代碼:
public record class Repository(string Name);上述程式代碼會定義類別來表示從 GitHub API 傳回的 JSON 物件。 您將使用此類別來顯示存放庫名稱的清單。
存放庫物件的 JSON 包含數十個屬性,但只有
Name屬性會被反序列化。 序列化器會自動忽略在目標類別中沒有相符的 JSON 屬性。 這項功能讓您更輕鬆地建立能處理大型 JSON 封包中某些欄位的類型。雖然您將在下一點中使用的方法有一個優點,即在屬性名稱方面不區分大小寫,但
GetFromJsonAsyncC# 慣例是將 屬性名稱的第一個字母大寫。使用該 HttpClientJsonExtensions.GetFromJsonAsync 方法提取 JSON 並將其轉換為 C# 物件。 以下列幾行取代 GetStringAsync(String) 方法中對
ProcessRepositoriesAsync的呼叫:var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos");更新的程式代碼會將 GetStringAsync(String) 取代為 HttpClientJsonExtensions.GetFromJsonAsync。
method 的
GetFromJsonAsync第一個引數是await運算式。await表示式幾乎可以出現在程序代碼中的任何位置,即使到目前為止,您也只會將其視為指派語句的一部分。 下一個參數是選擇性的,requestUri如果在建立client物件時已指定,則不需要提供。 您沒有為物件提供client要傳送要求的 URI,因此您現在指定了 URI。 最後一個選擇性參數 在程式碼片段中省略了 。CancellationToken該
GetFromJsonAsync方法是 通用的,這意味著您提供類型引數,說明應該從提取的 JSON 文本創建哪種對象。 在這個範例中,您會將反序列化成一個泛型物件List<Repository>,也就是 System.Collections.Generic.List<T>。List<T>類別會儲存物件的集合。 type 自變數會宣告儲存在List<T>中的物件類型。 類型參數是您的Repository紀錄,由於 JSON 文字代表存放庫物件的集合。新增程式代碼以顯示每個存放庫的名稱。 取代以下讀取的行:
Console.Write(json);使用下列程式碼:
foreach (var repo in repositories ?? Enumerable.Empty<Repository>()) Console.WriteLine(repo.Name);下列
using指示詞應該出現在檔案頂端:using System.Net.Http.Headers; using System.Net.Http.Json;執行應用程式。
dotnet run輸出是屬於 .NET Foundation 之存放庫名稱的清單。
重構程序代碼
ProcessRepositoriesAsync 方法可以執行異步工作,並傳回存放庫的集合。 將該方法變更為傳回 Task<List<Repository>>,然後將寫入主控台的代碼移到靠近其呼叫端的位置。
變更
ProcessRepositoriesAsync的簽章,以傳回其結果為Repository物件清單的工作:static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)處理 JSON 回應之後傳回存放庫:
var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos"); return repositories ?? new();編譯程式會產生傳回值的
Task<T>對象,因為您已將此方法標示為async。修改 Program.cs 檔案,以下列命令取代對
ProcessRepositoriesAsync的呼叫,以擷取結果,並將每個存放庫名稱寫入控制台。var repositories = await ProcessRepositoriesAsync(client); foreach (var repo in repositories) Console.WriteLine(repo.Name);執行應用程式。
輸出的結果相同。
反序列化更多屬性
在下列步驟中,我們會擴充程式碼,以處理 GitHub API 傳回的 JSON 承載中的更多屬性。 您可能不需要處理每個屬性,但新增一些屬性會示範其他 C# 功能。
把
Repository類別的內容取代成以下record定義。 請務必匯入System.Text.Json.Serialization命名空間,並套用[JsonPropertyName]屬性,以明確地將 JSON 欄位對應至 C# 屬性。using System.Text.Json.Serialization; public record class Repository( string Name, string Description, [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl, Uri Homepage, int Watchers, [property: JsonPropertyName("pushed_at")] DateTime LastPushUtc );Uri 和
int型別具有內建功能,可轉換到字串表示和從字串表示。 不需要額外的程式碼即可將 JSON 字串格式反序列化為這些目標型別。 如果 JSON 封包包含的數據未轉換成目標類型,則串行化動作會擲回例外狀況。JSON 通常會使用
lowercase或snake_case作為屬性名稱。 類似html_url的欄位不遵循 C# PascalCase 命名慣例。 使用[JsonPropertyName]可確保這些 JSON 索引鍵正確系結至其對應的 C# 屬性,即使其名稱大小寫不同或包含底線也一樣。 這種方法保證了可預測且穩定的反序列化,同時允許 C# 中的 PascalCase 屬性名稱。 此外,該GetFromJsonAsync方法是case-insensitive在匹配屬性名稱時,因此不需要進一步轉換。更新
foreach檔案中的 迴圈,以顯示屬性值:foreach (var repo in repositories) { Console.WriteLine($"Name: {repo.Name}"); Console.WriteLine($"Homepage: {repo.Homepage}"); Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}"); Console.WriteLine($"Description: {repo.Description}"); Console.WriteLine($"Watchers: {repo.Watchers:#,0}"); Console.WriteLine(); }執行應用程式。
清單現在包含附加屬性。
新增日期屬性
在 JSON 回應中,最後一次推送作業的日期會以此方式格式化:
2016-02-08T21:27:00Z
此格式適用於國際標準時間(UTC),因此還原串行化的結果為 DateTime 值,其 Kind 屬性為 Utc。
若要取得時區中所代表的日期和時間,您必須撰寫自定義轉換方法。
在 Repository.cs中,新增日期和時間 UTC 表示法的屬性,以及傳回轉換為當地時間日期的唯讀
LastPush屬性,檔案看起來應該如下所示:using System.Text.Json.Serialization; public record class Repository( string Name, string Description, [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl, Uri Homepage, int Watchers, [property: JsonPropertyName("pushed_at")] DateTime LastPushUtc ) { public DateTime LastPush => LastPushUtc.ToLocalTime(); }LastPush屬性是使用 表示式主體成員 來定義的,適用於get存取子。 沒有set存取器。 省略set存取子是定義 C# 中 只讀 屬性的一種方式。 (是,您可以在 C# 中建立 僅限寫入 屬性,但其值有限。在 Program.cs中新增另一個輸出語句:
Console.WriteLine($"Last push: {repo.LastPush}");完整的應用程式應該類似下列 Program.cs 檔案:
using System.Net.Http.Headers; using System.Net.Http.Json; using HttpClient client = new(); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json")); client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter"); var repositories = await ProcessRepositoriesAsync(client); foreach (var repo in repositories) { Console.WriteLine($"Name: {repo.Name}"); Console.WriteLine($"Homepage: {repo.Homepage}"); Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}"); Console.WriteLine($"Description: {repo.Description}"); Console.WriteLine($"Watchers: {repo.Watchers:#,0}"); Console.WriteLine($"{repo.LastPush}"); Console.WriteLine(); } static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client) { var repositories = await client.GetFromJsonAsync<List<Repository>>("https://api.github.com/orgs/dotnet/repos"); return repositories ?? new List<Repository>(); }執行應用程式。
輸出包含上次推送至每個存放庫的日期和時間。
後續步驟
在本教學課程中,您已建立應用程式來提出 Web 要求並剖析結果。 您的應用程式版本現在應該符合已完成 範例。
深入瞭解如何在 .NET 中設定 JSON 序列化 如何在 .NET 中序列化和反序列化(封送和解封送)JSON。