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
Abra um prompt de comando e crie um novo diretório para seu aplicativo. Torne ele o diretório atual.
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".
Navegue até o diretório "WebAPIClient" e execute o aplicativo.
cd WebAPIClient
dotnet run
dotnet run
executadotnet restore
automaticamente para restaurar as dependências de que o aplicativo precisa. Ele também executadotnet 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.
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 paraProcessRepositoriesAsync
, a qual usa a palavra-chaveawait
. - Define um método
ProcessRepositoriesAsync
vazio.
- Substitui a instrução
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.
- Um cabeçalho
- Configura cabeçalhos HTTP de todas as solicitações:
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.
Compile o aplicativo e execute-o.
dotnet run
Não há nenhum aviso de build porque o
ProcessRepositoriesAsync
agora contém um operadorawait
. 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.
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.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õesawait
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âmetrosJsonSerializerOptions
eCancellationToken
, 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 umList<Repository>
, que é outro objeto genérico, um System.Collections.Generic.List<T>. A classeList<T>
armazena uma coleção de objetos. O argumento type declara o tipo de objetos armazenados noList<T>
. O argumento de tipo é seu registroRepository
, pois o texto JSON representa uma coleção de objetos de repositório.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);
As seguintes diretivas
using
devem estar presentes na parte superior do arquivo:using System.Net.Http.Headers; using System.Text.Json;
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
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
paraName
. - Adiciona o JsonPropertyNameAttribute para especificar como essa propriedade aparece no JSON.
- Altera o nome da propriedade
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);
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.
Altere a assinatura de
ProcessRepositoriesAsync
para retornar uma tarefa cujo resultado é uma lista de objetosRepository
:static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
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 comoasync
.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);
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#.
Substitua o conteúdo da classe
Repository
pela seguinte definiçãorecord
: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.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(); }
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.
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 acessadorget
. Não há acessadorset
. Omitir o acessadorset
é 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.)Adicione outra instrução de saída em Program.cs novamente:
Console.WriteLine($"Last push: {repo.LastPush}");
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(); }
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.