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
Abra uma linha de comandos e crie um novo diretório para a sua aplicação. Torne-o no diretório atual.
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".
Navegue para o diretório "WebAPIClient" e execute a aplicação.
cd WebAPIClient
dotnet run
dotnet run
é executadodotnet restore
automaticamente para restaurar quaisquer dependências de que a aplicação necessite. Também é executadodotnet 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.
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 aProcessRepositoriesAsync
qual utiliza aawait
palavra-chave. - Define um método vazio
ProcessRepositoriesAsync
.
- Substitui a
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.
- Um
- Configura cabeçalhos HTTP para todos os pedidos:
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.
Crie a aplicação e execute-a.
dotnet run
Não existe nenhum aviso de compilação porque agora
ProcessRepositoriesAsync
contém umawait
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.
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.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
eCancellationToken
, 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 umList<Repository>
, que é outro objeto genérico, um System.Collections.Generic.List<T>. AList<T>
classe armazena uma coleção de objetos. O argumento type declara o tipo de objetos armazenados noList<T>
. O argumento type é o seuRepository
registo, porque o texto JSON representa uma coleção de objetos de repositório.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);
As seguintes
using
diretivas devem estar presentes na parte superior do ficheiro:using System.Net.Http.Headers; using System.Text.Json;
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
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 paraName
. - Adiciona o JsonPropertyNameAttribute para especificar como esta propriedade aparece no JSON.
- Altera o nome da
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);
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.
Altere a assinatura de
ProcessRepositoriesAsync
para devolver uma tarefa cujo resultado é uma lista deRepository
objetos:static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
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 comoasync
.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);
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#.
Substitua os conteúdos da
Repository
classe pela seguinterecord
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.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(); }
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.
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 oget
acessório. Não háset
acessório. Omitir oset
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.)Adicione outra instrução de saída em Program.cs: novamente:
Console.WriteLine($"Last push: {repo.LastPush}");
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(); }
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.