チュートリアル: 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. コンソール ウィンドウに次のコマンドを入力します。

    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 を貼り付けて、受信および処理する情報を確認できます。

HTTP 要求を行うには、HttpClient クラスを使用します。 実行時間の長い API の場合、HttpClient によってサポートされるのは非同期メソッドのみです。 したがって、以下の手順では、非同期メソッドを作成して、Main メソッドから呼び出します。

  1. プロジェクト ディレクトリ内の Program.cs ファイルを開いて、その内容を次に置き換えます。

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

    このコードによって以下が行われます。

    • Console.WriteLine ステートメントを、await キーワードを使用する ProcessRepositoriesAsync の呼び出しに置き換えます。
    • 空の 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 メソッドで、.NET Foundation 組織の下にあるすべてのリポジトリの一覧を返す GitHub エンドポイントを呼び出します。

     static async Task ProcessRepositoriesAsync(HttpClient client)
     {
         var json = await client.GetStringAsync(
             "https://api.github.com/orgs/dotnet/repos");
    
         Console.Write(json);
     }
    

    このコードによって以下が行われます。

    • HttpClient.GetStringAsync(String) メソッドの呼び出しから返されるタスクを待機します。 このメソッドは、指定された URI に HTTP GET 要求を送信します。 応答の本文は String として返されます。これは、タスクが完了すると使用できるようになります。
    • 応答文字列 json がコンソールに出力されます。
  4. アプリをビルドして実行します。

    dotnet run
    

    ProcessRepositoriesAsyncawait 演算子が含まれるようになったため、ビルドで警告は発生しません。 出力は、JSON テキストの長い表示になります。

JSON の結果を逆シリアル化する

次の手順では、JSON の応答を C# オブジェクトに変換します。 JSON をオブジェクトに逆シリアル化するには、System.Text.Json.JsonSerializer クラスを使用します。

  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 式は、これまでは代入ステートメントの一部としてしか目にしませんでしたが、コード内のほとんどの場所で使用できます。 他の 2 つのパラメーター JsonSerializerOptionsCancellationToken は省略可能で、このコード スニペットでは省略されています。

    DeserializeAsync メソッドは "ジェネリック" です。つまり、JSON テキストから作成する必要があるオブジェクトの種類に対して型引数を指定します。 この例では、List<Repository> に逆シリアル化しています。これは、別のジェネリック オブジェクト System.Collections.Generic.List<T> です。 List<T> クラスには、オブジェクトのコレクションが格納されます。 型引数は、List<T> に格納されているオブジェクトの型を宣言します。 JSON テキストはリポジトリ オブジェクトのコレクションを表すため、型引数は Repository レコードです。

  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. 結果が Repository オブジェクトのリストであるタスクを返すように ProcessRepositoriesAsync のシグネチャを変更します。

    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();
    

    このメソッドを async とマークしたので、コンパイラにより戻り値として Task<T> オブジェクトが生成されます。

  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);
    

    Uri 型と int 型には、文字列表現との間で変換を行うための機能が組み込まれています。 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# で "読み取り専用" プロパティを定義する 1 つの方法です。 (C# では "書き込み専用" プロパティを作成できますが、その値は制限されています)。

  2. Program.cs にもう 1 つの出力ステートメントを追加します。

    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 のシリアル化と逆シリアル化 (マーシャリングとマーシャリングの解除) を行う方法」で、JSON のシリアル化を構成する詳しい方法を学習してください。