閱讀英文

共用方式為


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

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

本教學課程顯示如何:

  • 傳送 HTTP 要求。
  • 還原序列化 JSON 回應。
  • 使用屬性來設定還原序列化。

如果您偏好追蹤本教學課程的最終範例,則可以將其下載。 如需下載指示,請參閱範例和教學課程

必要條件

  • .NET SDK 6.0 或更新版本
  • Visual Studio Code (開放原始碼、跨平台編輯器) 這類程式碼編輯器。 您可以在 Windows、Linux、macOS 或 Docker 容器中執行範例應用程式。

建立用戶端應用程式

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

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

    .NET CLI
    dotnet new console --name WebAPIClient
    

    此命令會建立基本 "Hello World" 應用程式的起始檔案。 專案名稱為 "WebAPIClient"。

  3. 導覽至 "WebAPIClient" 目錄,然後執行應用程式。

    .NET CLI
    cd WebAPIClient
    
    .NET CLI
    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 的非同步方法。 因此,下列步驟會建立非同步方法,並從 Main 方法進行呼叫。

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

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

    此程式碼:

    • Console.WriteLine 陳述式取代為使用 await 關鍵字的 ProcessRepositoriesAsync 呼叫。
    • 定義空的 ProcessRepositoriesAsync 方法。
  2. Program 類別中,使用 HttpClient 來處理要求和回應,方法是將內容取代為下列 C#。

    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 基礎組織下所有存放庫的清單:

    C#
     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. 請建置並執行應用程式。

    .NET CLI
    dotnet run
    

    沒有建置警告,因為 ProcessRepositoriesAsync 現在包含 await 運算子。 輸出是 JSON 文字的完整顯示。

還原序列化 JSON 結果

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

  1. 建立名為 Repository.cs 的檔案,然後新增下列程式碼:

    C#
    public record class Repository(string name);
    

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

    存放庫物件的 JSON 包含數十個屬性,但只會還原序列化 name 屬性。 序列化程式會自動忽略目標類別中沒有相符項目的 JSON 屬性。 此功能可讓您更輕鬆地建立類型,而類型只與大型 JSON 封包中的欄位子集搭配運作。

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

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

    C#
    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> 類別可儲存物件的集合。 類型引數會宣告 List<T> 中所儲存的物件類型。 類型引數是您的 Repository 記錄,因為 JSON 文字代表存放庫物件的集合。

  3. 新增程式碼,以顯示每個存放庫的名稱。 將下列幾行:

    C#
    Console.Write(json);
    

    取代為下列程式碼:

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

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

    .NET CLI
    dotnet run
    

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

設定還原序列化

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

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

    此程式碼:

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

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

    輸出會相同。

重構程式碼

ProcessRepositoriesAsync 方法可以執行非同步工作,然後傳回儲存機制的集合。 變更該方法以傳回 Task<List<Repository>>,並將寫入至主控台的程式碼移至其呼叫端附近。

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

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

    C#
    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 呼叫取代為下列內容來擷取結果,然後將每個存放庫名稱寫入至主控台。

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

    輸出會相同。

還原序列化更多屬性

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

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

    C#
    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 迴圈,以顯示內容值:

    C#
    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 回應中,會以此形式格式化最後一個推送作業的日期:

JSON
2016-02-08T21:27:00Z

此格式適用於國際標準時間 (UTC),因此還原序列化的結果是其 Kind 屬性為 UtcDateTime 值。

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

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

    C#
    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 中新增另一個輸出陳述式:

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

    C#
    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 要求以及剖析結果。 您的應用程式版本現在應該與已完成範例相符。

若要深入瞭解如何設定 JSON 序列化,請參閱 如何在 .NET 中序列化和還原序列化 (封送處理和 unmarshal) JSON