Tutorial: Fazer solicitações HTTP em um aplicativo de console .NET usando C #

Esse tutorial cria um aplicativo que emite solicitações HTTP para um serviço REST no GitHub. O aplicativo lê informações no formato JSON e converte o JSON em objetos C#. A conversão de objetos JSON em C# é conhecida como desserialização.

Este tutorial mostra como:

  • Enviar solicitações HTTP.
  • Desserializar respostas JSON.
  • Configurar a desserialização com atributos.

Se preferir seguir com o exemplo final desse tutorial, você pode baixá-lo. Para obter instruções de download, consulte Exemplos e tutoriais.

Pré-requisitos

  • SDK do .NET 6.0 ou posterior
  • Um editor de código como [Visual Studio Code (um editor de software de código aberto e multiplataforma). Você pode executar o aplicativo de exemplo no Windows, Linux ou macOS ou em um contêiner do Docker.

Criar o aplicativo cliente

  1. Abra um prompt de comando e crie um novo diretório para seu aplicativo. Torne ele o diretório atual.

  2. Insira o seguinte comando em uma janela do console:

    dotnet new console --name WebAPIClient
    

    Esse comando cria os arquivos iniciais de um aplicativo "Hello World" básico. O nome do projeto é "WebAPIClient".

  3. Navegue até o diretório "WebAPIClient" e execute o aplicativo.

    cd WebAPIClient
    
    dotnet run
    

    dotnet run executa dotnet restore automaticamente para restaurar as dependências de que o aplicativo precisa. Ele também executa dotnet build se necessário. Você deve ver a saída do aplicativo"Hello, World!". No terminal, pressione Ctrl+C para interromper o aplicativo.

Fazer solicitações HTTP

Esse aplicativo chama a API do GitHub para obter informações sobre os projetos no escopo do .NET Foundation. O ponto de extremidade é https://api.github.com/orgs/dotnet/repos. Para recuperar informações, ele faz uma solicitação HTTP GET. O navegador também faz solicitações HTTP GET, para que você possa colar essa URL na barra de endereços do seu navegador e ver as informações recebidas e processadas.

Use a classe HttpClient para fazer solicitações HTTP. HttpClient dá suporte apenas a métodos assíncronos para suas APIs de execução longa. Portanto, as etapas a seguir criam um método assíncrono e o chamam do método Main.

  1. Abra o arquivo Program.cs no diretório do projeto e substitua seu conteúdo pelo seguinte:

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

    Esse código:

    • Substitui a instrução Console.WriteLine por uma chamada para ProcessRepositoriesAsync, a qual usa a palavra-chave await.
    • Define um método ProcessRepositoriesAsync vazio.
  2. Na classe Program, use um HttpClient para manipular solicitações e respostas ao substituir o conteúdo pelo C# a seguir.

    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)
    {
    }
    

    Esse código:

    • Configura cabeçalhos HTTP de todas as solicitações:
      • Um cabeçalho Accept para aceitar respostas JSON
      • Um cabeçalho User-Agent. Esses cabeçalhos são verificados pelo código do servidor GitHub e são necessários para recuperar informações do GitHub.
  3. No método ProcessRepositoriesAsync, chame o ponto de extremidade do GitHub que retorna uma lista de todos os repositórios na organização do .NET Foundation:

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

    Esse código:

    • Aguarda a tarefa retornada do método de chamada HttpClient.GetStringAsync(String). Esse método envia uma solicitação HTTP GET para o URI especificado. O corpo da resposta é retornado como um String, que está disponível quando a tarefa é concluída.
    • A cadeia de caracteres de resposta json é impressa no console.
  4. Compile o aplicativo e execute-o.

    dotnet run
    

    Não há nenhum aviso de build porque o ProcessRepositoriesAsync agora contém um operador await. A saída é uma longa exibição de texto JSON.

Desserializar o resultado JSON

As etapas a seguir convertem a resposta JSON em objetos C#. Use a classe System.Text.Json.JsonSerializer para desserializar o JSON em objetos.

  1. Crie um arquivo chamado Repository.cs e adicione o código a seguir:

    public record class Repository(string name);
    

    O código anterior define uma classe para representar o objeto JSON retornado da API do GitHub. Você usará essa classe para exibir uma lista de nomes de repositório.

    O JSON de um objeto de repositório contém dezenas de propriedades, mas apenas a propriedade name será desserializada. O serializador ignora automaticamente as propriedades JSON para as quais não há correspondência na classe de destino. Esse recurso facilita a criação de tipos que funcionam apenas com um subconjunto de campos em um pacote JSON grande.

    A convenção C# é capitalizar a primeira letra de nomes de propriedade, mas a propriedade name aqui começa com uma letra minúscula porque corresponde exatamente ao que está no JSON. Posteriormente, você verá como usar nomes de propriedade C# que não correspondem aos nomes de propriedade JSON.

  2. Use o serializador para converter JSON em objetos C#. Substitua a chamada para GetStringAsync(String) no método ProcessRepositoriesAsync pelas duas linhas a seguir:

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

    O código atualizado substitui GetStringAsync(String) por GetStreamAsync(String). Esse método de serializador usa um fluxo, em vez de uma cadeia de caracteres, como sua fonte.

    O primeiro argumento JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) é uma expressão await. Expressões await podem aparecer em quase todo lugar em seu código, apesar de que até o momento, você apenas as viu como parte de uma instrução de atribuição. Os outros dois parâmetros JsonSerializerOptions e CancellationToken, são opcionais e são omitidos no snippet de código.

    O método DeserializeAsync é genérico, o que significa que você fornece argumentos de tipo para que tipo de objetos devem ser criados a partir do texto JSON. Neste exemplo, você está desserializando para um List<Repository>, que é outro objeto genérico, um System.Collections.Generic.List<T>. A classe List<T> armazena uma coleção de objetos. O argumento type declara o tipo de objetos armazenados no List<T>. O argumento de tipo é seu registro Repository, pois o texto JSON representa uma coleção de objetos de repositório.

  3. Adicione código para exibir o nome de cada repositório. Substitua as linhas que mostram:

    Console.Write(json);
    

    pelo código a seguir:

    foreach (var repo in repositories ?? Enumerable.Empty<Repository>())
        Console.Write(repo.name);
    
  4. As seguintes diretivas using devem estar presentes na parte superior do arquivo:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
  5. Execute o aplicativo.

    dotnet run
    

    A saída é uma lista dos nomes dos repositórios que fazem parte do .NET Foundation.

Configurar a desserialização

  1. Em Repository.cs, substitua o conteúdo do arquivo pelo C# a seguir.

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

    Esse código:

    • Altera o nome da propriedade name para Name.
    • Adiciona o JsonPropertyNameAttribute para especificar como essa propriedade aparece no JSON.
  2. Em Program.cs, atualize o código para usar a nova capitalização da propriedade Name:

    foreach (var repo in repositories)
       Console.Write(repo.Name);
    
  3. Execute o aplicativo.

    A saída é a mesma.

Refatorar o código

O método ProcessRepositoriesAsync pode fazer o trabalho assíncrono e retornar uma coleção de repositórios. Altere esse método para retornar Task<List<Repository>> e mova o código que grava no console perto do chamador.

  1. Altere a assinatura de ProcessRepositoriesAsync para retornar uma tarefa cujo resultado é uma lista de objetos Repository:

    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    
  2. Retorne os repositórios depois de processar a resposta 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();
    

    O compilador gera o objeto Task<T> para o calor de retorno porque você marcou esse método como async.

  3. Modifique o arquivo Program.cs, substituindo a chamada para ProcessRepositoriesAsync pelo seguinte para capturar os resultados e gravar cada nome do repositório no console.

    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
        Console.Write(repo.Name);
    
  4. Execute o aplicativo.

    A saída é a mesma.

Desserializar mais propriedades

As etapas a seguir adicionam código para processar mais das propriedades no pacote JSON recebido. Você provavelmente não vai querer processar todas as propriedades, mas adicionar mais algumas demonstra outros recursos de C#.

  1. Substitua o conteúdo da classe Repository pela seguinte definição 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);
    

    Os tipos Uri e int têm funcionalidade interna para converter de e para a representação de cadeia de caracteres. Nenhum código extra é necessário para desserializar do formato de cadeia de caracteres JSON para esses tipos de destino. Se o pacote JSON contiver dados que não são convertidos em um tipo de destino, a ação de serialização gerará uma exceção.

  2. Atualize o loop foreach no arquivo Program.cs para exibir os valores da propriedade:

    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. Execute o aplicativo.

    A lista agora inclui as propriedades adicionais.

Adicionar uma propriedade date

A data da última operação de push é formatada dessa forma na resposta JSON:

2016-02-08T21:27:00Z

Esse formato é para UTC (Tempo Universal Coordenado), portanto, o resultado da desserialização é um valor DateTime cuja propriedade Kind é Utc.

Para obter uma data e hora representadas em seu fuso horário, você precisa escrever um método de conversão personalizado.

  1. Em Repository.cs, adicione uma propriedade para a representação UTC da data e hora e uma propriedade LastPush somente leitura que retorna a data convertida em hora local. O arquivo deve se parecer com o seguinte:

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

    A propriedade LastPush é definida usando um membro com corpo de expressão para o acessador get. Não há acessador set . Omitir o acessador set é uma maneira de definir uma propriedade somente leitura em C#. (Sim, você pode criar propriedades somente gravação em C#, mas o valor delas é limitado.)

  2. Adicione outra instrução de saída em Program.cs novamente:

    Console.WriteLine($"Last push: {repo.LastPush}");
    
  3. O aplicativo completo deve ser semelhante ao seguinte arquivo 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. Execute o aplicativo.

    A saída inclui a data e a hora do último push de cada repositório.

Próximas etapas

Neste tutorial, você criou um aplicativo que faz solicitações da Web e analisa os resultados. Agora, sua versão do aplicativo deve corresponder ao exemplo finalizado.

Saiba mais sobre como configurar a serialização JSON em Como serializar e desserializar (marshal and unmarshal) JSON no .NET.