Tutorial: Fazer pedidos HTTP numa aplicação de consola .NET com C #

Este tutorial cria uma aplicação que emite pedidos HTTP para um serviço REST no GitHub. A aplicação 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.

O tutorial mostra como:

  • Enviar pedidos HTTP.
  • Anular a serialização das respostas JSON.
  • Configurar a desserialização com atributos.

Se preferir acompanhar o exemplo final deste tutorial, pode transferi-lo. Para obter instruções de transferência, veja Exemplos e Tutoriais.

Pré-requisitos

  • SDK .NET 6.0 ou posterior
  • Um editor de código, como [Visual Studio Code (um editor open source e multiplataforma). Pode executar a aplicação de exemplo no Windows, Linux ou macOS ou num contentor do Docker.

Criar a aplicação cliente

  1. Abra uma linha de comandos e crie um novo diretório para a sua aplicação. Torne-o no diretório atual.

  2. Introduza o seguinte comando numa janela da consola:

    dotnet new console --name WebAPIClient
    

    Este comando cria os ficheiros de arranque para uma aplicação "Hello World" básica. O nome do projeto é "WebAPIClient".

  3. Navegue para o diretório "WebAPIClient" e execute a aplicação.

    cd WebAPIClient
    
    dotnet run
    

    dotnet run é executado dotnet restore automaticamente para restaurar quaisquer dependências de que a aplicação necessite. Também é executado dotnet build , se necessário. Deverá ver o resultado "Hello, World!"da aplicação . No terminal, prima Ctrl+C para parar a aplicação.

Fazer pedidos HTTP

Esta aplicação chama a API do GitHub para obter informações sobre os projetos sob o comando .NET Foundation . O ponto final é https://api.github.com/orgs/dotnet/repos. Para obter informações, faz um pedido HTTP GET. Os browsers também fazem pedidos HTTP GET, para que possa colar esse URL na barra de endereço do browser para ver que informações irá receber e processar.

Utilize a HttpClient classe para fazer pedidos HTTP. HttpClient suporta apenas métodos assíncronos para as apIs de execução prolongada. Assim, os passos seguintes criam um método assíncrono e chamam-no a partir do método Main.

  1. Abra o Program.cs ficheiro no diretório do projeto e substitua os respetivos conteúdos pelo seguinte:

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

    Este código:

    • Substitui a Console.WriteLine instrução por uma chamada para a ProcessRepositoriesAsync qual utiliza a await palavra-chave.
    • Define um método vazio ProcessRepositoriesAsync .
  2. Program Na classe , utilize um HttpClient para processar pedidos e respostas ao substituir o conteúdo pelo seguinte 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)
    {
    }
    

    Este código:

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

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

    Este código:

    • Aguarda a tarefa devolvida pelo método de chamada HttpClient.GetStringAsync(String) . Este método envia um pedido HTTP GET para o URI especificado. O corpo da resposta é devolvido como um String, que está disponível quando a tarefa é concluída.
    • A cadeia de json resposta é impressa na consola.
  4. Crie a aplicação e execute-a.

    dotnet run
    

    Não existe nenhum aviso de compilação porque agora ProcessRepositoriesAsync contém um await operador. O resultado é uma longa apresentação de texto JSON.

Anular a serialização do Resultado JSON

Os passos seguintes convertem a resposta JSON em objetos C#. Utilize a classe para anular a System.Text.Json.JsonSerializer serialização do JSON em objetos.

  1. Crie um ficheiro com o nome Repository.cs e adicione o seguinte código:

    public record class Repository(string name);
    

    O código anterior define uma classe para representar o objeto JSON devolvido pela API do GitHub. Irá utilizar esta classe para apresentar uma lista de nomes de repositórios.

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

    A convenção C# é colocar em maiúsculas a primeira letra de nomes de propriedade, mas a name propriedade aqui começa com uma letra minúscula porque corresponde exatamente ao que está no JSON. Mais tarde, verá como utilizar nomes de propriedade C# que não correspondem aos nomes das propriedades JSON.

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

    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 por GetStringAsync(String)GetStreamAsync(String). Este método de serializador utiliza um fluxo em vez de uma cadeia como a respetiva origem.

    O primeiro argumento a JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) é uma expressão await . await As expressões podem aparecer em praticamente qualquer parte do seu código, embora até agora só as tenha visto 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 fragmento de código.

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

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

    Console.Write(json);
    

    com o seguinte código:

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

    using System.Net.Http.Headers;
    using System.Text.Json;
    
  5. Execute a aplicação.

    dotnet run
    

    O resultado é 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 ficheiro pelo seguinte C#.

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

    Este código:

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

    foreach (var repo in repositories)
       Console.Write(repo.Name);
    
  3. Execute a aplicação.

    O resultado é o mesmo.

Refatorizar o código

O ProcessRepositoriesAsync método pode fazer o trabalho assíncrono e devolver uma coleção dos repositórios. Altere esse método para devolver Task<List<Repository>>e mova o código que escreve para a consola perto do respetivo autor da chamada.

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

    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    
  2. Devolva 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 Task<T> objeto para o valor devolvido porque marcou este método como async.

  3. Modifique o ficheiro Program.cs , substituindo a chamada para ProcessRepositoriesAsync pelo seguinte para capturar os resultados e escrever cada nome de repositório na consola do .

    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
        Console.Write(repo.Name);
    
  4. Execute a aplicação.

    O resultado é o mesmo.

Anular a serialização de mais propriedades

Os passos seguintes adicionam código para processar mais propriedades no pacote JSON recebido. Provavelmente não vai querer processar todas as propriedades, mas adicionar mais algumas demonstra outras funcionalidades de C#.

  1. Substitua os conteúdos da Repository classe pela seguinte record definição:

    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 Uri tipos e int têm funcionalidade incorporada para converter para e a partir da representação de cadeia. Não é necessário nenhum código adicional para anular a serialização do formato de cadeia JSON para esses tipos de destino. Se o pacote JSON contiver dados que não são convertidos num tipo de destino, a ação de serialização gera uma exceção.

  2. Atualize o foreach ciclo no ficheiro Program.cs para apresentar 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 a aplicação.

    A lista inclui agora as propriedades adicionais.

Adicionar uma propriedade de data

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

2016-02-08T21:27:00Z

Este formato destina-se à Hora Universal Coordenada (UTC), pelo que o resultado da desserialização é um DateTime valor cuja Kind propriedade é Utc.

Para obter uma data e hora representadas no seu fuso horário, tem de 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 só de leitura LastPush que devolva a data convertida para a hora local, o ficheiro deve ter o seguinte aspeto:

    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 LastPush propriedade é definida com um membro encorpado de expressão para o get acessório. Não há set acessório. Omitir o set acessório é uma forma de definir uma propriedade só de leitura em C#. (Sim, pode criar propriedades só de escrita em C#, mas o respetivo valor é limitado.)

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

    Console.WriteLine($"Last push: {repo.LastPush}");
    
  3. A aplicação completa deve assemelhar-se ao seguinte ficheiro 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 a aplicação.

    O resultado inclui a data e hora do último push para cada repositório.

Passos seguintes

Neste tutorial, criou uma aplicação que faz pedidos Web e analisa os resultados. A sua versão da aplicação deverá agora corresponder ao exemplo concluído.

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