다음을 통해 공유


자습서: 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에 대한 비동기 메서드만 지원합니다. 따라서 다음 단계에서는 비동기 메서드를 만들고 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 헤더를 설정합니다.
      • Accept JSON 응답을 수락하는 헤더
      • 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) 메서드에서 반환된 작업을 기다립니다. 이 메서드는 HTTP GET 요청을 지정된 URI로 보냅니다. 응답 본문은 작업이 완료되면 String로 반환됩니다.
    • 응답 문자열 json 이 콘솔에 인쇄됩니다.
  4. 앱을 빌드하고 실행합니다.

    dotnet run
    

    await 연산자가 이제 ProcessRepositoriesAsync에 포함되어 있으므로 빌드 경고가 없습니다. 출력은 JSON 텍스트의 긴 표시입니다.

JSON 결과 역직렬화

다음 단계에서는 JSON 응답을 C# 개체로 변환합니다. 클래스를 System.Text.Json.JsonSerializer 사용하여 JSON을 개체로 역직렬화합니다.

  1. Repository.cs 파일을 만들고 다음 코드를 추가합니다.

    public record class Repository(string name);
    

    앞의 코드는 GitHub API에서 반환된 JSON 개체를 나타내는 클래스를 정의합니다. 이 클래스를 사용하여 리포지토리 이름 목록을 표시합니다.

    리포지토리 개체의 JSON에는 수십 개의 속성이 name 포함되지만 속성만 역직렬화됩니다. serializer는 대상 클래스에 일치하는 항목이 없는 JSON 속성을 자동으로 무시합니다. 이 기능을 사용하면 큰 JSON 패킷에 있는 필드의 하위 집합만 사용하는 형식을 더 쉽게 만들 수 있습니다.

    C# 규칙은 속성 이름의 첫 글자를 대문자로 하는 것이지만 name , 여기서 속성은 JSON의 내용과 정확히 일치하기 때문에 소문자로 시작합니다. 나중에 JSON 속성 이름과 일치하지 않는 C# 속성 이름을 사용하는 방법을 확인할 수 있습니다.

  2. serializer를 사용하여 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)으로 바꿉니다. 이 serializer 메서드는 문자열 대신 스트림을 원본으로 사용합니다.

    첫 번째 인수 JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken)await 식입니다. await 표현식은 코드 내 거의 모든 위치에 나타날 수 있지만, 지금까지는 할당문에서만 본 적이 있을 것입니다. 다른 두 매개 변수인 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 .로 변경합니다.
    • JSON에 JsonPropertyNameAttribute 이 속성이 표시되는 방식을 지정하는 데 추가합니다.
  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();
    

    컴파일러는 이 메서드를 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);
    

    Uriint 형식에는 문자열 표현으로 변환하거나 문자열 표현으로부터 변환할 수 있는 기본 제공 기능이 있습니다. JSON 문자열 형식에서 해당 대상 형식으로 역직렬화하는 데 추가 코드가 필요하지 않습니다. JSON 패킷에 대상 형식으로 변환되지 않는 데이터가 포함된 경우 serialization 작업은 예외를 throw합니다.

  2. foreach Program.cs 파일의 루프를 업데이트하여 속성 값을 표시합니다.

    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. 앱을 실행합니다.

    출력에는 각 리포지토리에 대한 마지막 푸시의 날짜와 시간이 포함됩니다.

다음 단계

이 자습서에서는 웹 요청을 만들고 결과를 구문 분석하는 앱을 만들었습니다. 이제 앱 버전이 완성된 샘플과 일치해야 합니다.

.NET에서 JSON을 직렬화 및 역직렬화(마샬링 및 언마샬링)하는 방법을 구성하는 방법에 대해 자세히 알아봅니다.