共用方式為


教學課程:使用 C 在 .NET 控制台應用程式中提出 HTTP 要求#

本教學課程會建置在 GitHub 上對 REST 服務發出 HTTP 要求的應用程式。 應用程式會以 JSON 格式讀取資訊,並將 JSON 轉換成 C# 物件。 從 JSON 轉換成 C# 物件稱為 還原串行化

本教學課程示範如何:

  • 傳送 HTTP 要求。
  • 解序列化 JSON 回應。
  • 使用屬性設定反序列化。

如果您想要遵循本教學課程的 最終範例,您可以下載。 如需下載指示,請參閱 範例和教學課程

先決條件

建立用戶端應用程式

  1. 開啟命令提示字元,併為您的應用程式建立新的目錄。 讓該目錄成為目前的目錄。

  2. 在主控台視窗中輸入下列命令:

    dotnet new console --name WebAPIClient
    

    此命令會建立基本 「Hello World」 應用程式的入門檔案。 專案名稱為 「WebAPIClient」。。

  3. 流覽至 「WebAPIClient」 目錄,然後執行應用程式。

    cd WebAPIClient
    
    dotnet run
    

    dotnet 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 的異步方法。 因此,以下步驟將建立異步方法,並從主程式方法呼叫它。

  1. 在您的專案目錄中開啟 Program.cs 檔案,並以下列內容取代其內容:

    await ProcessRepositoriesAsync();
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    此程式碼:

    • Console.WriteLine 語句替換為呼叫 ProcessRepositoriesAsync 並使用 await 關鍵詞。
    • 定義空的 ProcessRepositoriesAsync 方法。
  2. 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 擷取資訊是必要的。
  3. 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 會列印至主控台。
  4. 建置應用程式並加以執行。

    dotnet run
    

    沒有建置警告,因為 ProcessRepositoriesAsync 現在包含 await 運算符。 輸出是一段冗長的 JSON 文字。

反序列化 JSON 結果

下列步驟會將 JSON 回應轉換成 C# 物件。 您可以使用 System.Text.Json.JsonSerializer 類別將 JSON 還原串行化為物件。

  1. 建立名為 Repository.cs 的檔案,並新增下列程序代碼:

    public record class Repository(string name);
    

    上述程式代碼會定義類別來表示從 GitHub API 傳回的 JSON 物件。 您將使用此類別來顯示存放庫名稱的清單。

    存放庫物件的 JSON 包含數十個屬性,但只有 name 屬性會被反序列化。 序列化器會自動忽略在目標類別中沒有相符的 JSON 屬性。 這項功能讓您更輕鬆地建立能處理大型 JSON 封包中某些欄位的類型。

    C# 慣例是 將屬性名稱的第一個字母大寫,但這裡的 name 屬性會以小寫字母開頭,因為這完全符合 JSON 中的內容。 稍後您將瞭解如何使用不符合 JSON 屬性名稱的 C# 屬性名稱。

  2. 使用串行化程式將 JSON 轉換成 C# 物件。 以下列幾行取代 ProcessRepositoriesAsync 方法中對 GetStringAsync(String) 的呼叫:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    

    更新的程式代碼會將 GetStringAsync(String) 取代為 GetStreamAsync(String)。 這個串行化程式方法會使用數據流,而不是字串做為其來源。

    JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) 的第一個參數是 await 表達式。 await 表示式幾乎可以出現在程序代碼中的任何位置,即使到目前為止,您也只會將其視為指派語句的一部分。 其他兩個參數,JsonSerializerOptionsCancellationToken,都是選擇性的,而且會在代碼段中省略。

    DeserializeAsync 方法是 泛型,這表示您需要提供物件類型的型別參數,以從 JSON 文字建立物件。 在這個範例中,您會將反序列化成一個泛型物件 List<Repository>,也就是 System.Collections.Generic.List<T>List<T> 類別會儲存物件的集合。 type 自變數會宣告儲存在 List<T>中的物件類型。 類型參數是您的 Repository 紀錄,由於 JSON 文字代表存放庫物件的集合。

  3. 新增程式代碼以顯示每個存放庫的名稱。 取代以下讀取的行:

    Console.Write(json);
    

    使用下列程式碼:

    foreach (var repo in repositories ?? Enumerable.Empty<Repository>())
        Console.Write(repo.name);
    
  4. 下列 using 指示詞應該出現在檔案頂端:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
  5. 執行應用程式。

    dotnet run
    

    輸出是屬於 .NET Foundation 之存放庫名稱的清單。

設定還原串行化

  1. Repository.cs中,以下列 C# 取代檔案內容。

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name);
    

    此程式碼:

    • name 屬性的名稱變更為 Name
    • 新增 JsonPropertyNameAttribute,以指定這個屬性在 JSON 中的顯示方式。
  2. Program.cs中,將程式代碼更新為使用 Name 屬性的新字母大小寫:

    foreach (var repo in repositories)
       Console.Write(repo.Name);
    
  3. 執行應用程式。

    輸出的結果相同。

重構程序代碼

ProcessRepositoriesAsync 方法可以執行異步工作,並傳回存放庫的集合。 將該方法變更為傳回 Task<List<Repository>>,然後將寫入主控台的代碼移到靠近其呼叫端的位置。

  1. 變更 ProcessRepositoriesAsync 的簽章,以傳回其結果為 Repository 物件清單的工作:

    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    
  2. 處理 JSON 回應之後傳回存放庫:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    return repositories ?? new();
    

    編譯程式會產生傳回值的 Task<T> 對象,因為您已將此方法標示為 async

  3. 修改 Program.cs 檔案,以下列命令取代對 ProcessRepositoriesAsync 的呼叫,以擷取結果,並將每個存放庫名稱寫入控制台。

    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
        Console.Write(repo.Name);
    
  4. 執行應用程式。

    輸出相同。

反序列化更多屬性

下列步驟會新增程序代碼,以處理所接收 JSON 封包中的更多屬性。 您可能不想要處理每一個屬性,但新增幾個能展示 C# 的其他功能。

  1. Repository 類別的內容取代為下列 record 定義:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers);
    

    Uriint 型別具有內建功能,可轉換到字串表示和從字串表示。 不需要額外的程式碼即可將 JSON 字串格式反序列化為這些目標型別。 如果 JSON 封包包含的數據未轉換成目標類型,則串行化動作會擲回例外狀況。

  2. 更新 Program.cs 檔案中的 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();
    }
    
  3. 執行應用程式。

    清單現在包含附加屬性。

新增日期屬性

在 JSON 回應中,最後一次推送作業的日期會以此方式格式化:

2016-02-08T21:27:00Z

此格式適用於國際標準時間(UTC),因此還原串行化的結果為 DateTime 值,其 Kind 屬性為 Utc

若要取得時區中所代表的日期和時間,您必須撰寫自定義轉換方法。

  1. Repository.cs中,新增日期和時間 UTC 表示法的屬性,以及傳回轉換為當地時間日期的唯讀 LastPush 屬性,檔案看起來應該如下所示:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers,
        [property: JsonPropertyName("pushed_at")] DateTime LastPushUtc)
    {
        public DateTime LastPush => LastPushUtc.ToLocalTime();
    }
    

    LastPush 屬性是使用 表示式主體成員 來定義的,適用於 get 存取子。 沒有 set 存取器。 省略 set 存取子是定義 C# 中 只讀 屬性的一種方式。 (是,您可以在 C# 中建立 僅限寫入 屬性,但其值有限。

  2. Program.cs中新增另一個輸出語句:

    Console.WriteLine($"Last push: {repo.LastPush}");
    
  3. 完整的應用程式應該類似下列 Program.cs 檔案:

    using System.Net.Http.Headers;
    using System.Text.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)
    {
        await using Stream stream =
            await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
        var repositories =
            await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
        return repositories ?? new();
    }
    
  4. 執行應用程式。

    輸出包含上次推送至每個存放庫的日期和時間。

後續步驟

在本教學課程中,您已建立應用程式來提出 Web 要求並剖析結果。 您的應用程式版本現在應該符合已完成 範例

深入瞭解如何在 .NET 中設定 JSON 序列化 如何在 .NET 中序列化和反序列化(封送和解封送)JSON