Tutorial: Realización de solicitudes HTTP en una aplicación de consola de .NET mediante C#
En este tutorial se crea una aplicación que emite solicitudes HTTP a un servicio REST en GitHub. La aplicación lee información en formato JSON y convierte la respuesta JSON en objetos de C#. La conversión de JSON en objetos de C# se conoce como deserialización.
En el tutorial se muestra cómo hacer lo siguiente:
- Enviar solicitudes HTTP
- Deserializar respuestas JSON
- Configurar la deserialización con atributos
Si prefiere seguir las explicaciones con el ejemplo final del tutorial, puede descargarlo. Para obtener instrucciones de descarga, vea Ejemplos y tutoriales.
Requisitos previos
- SDK de .NET 6.0 o versiones posteriores.
- Un editor de código como [Visual Studio Code (un editor multiplataforma de código abierto). Puede ejecutar la aplicación de ejemplo en Windows, Linux o macOS, o bien en un contenedor de Docker.
Creación de la aplicación cliente
Abra un símbolo del sistema y cree un directorio para la aplicación. Conviértalo en el directorio actual.
Escriba el siguiente comando en la ventana de consola:
dotnet new console --name WebAPIClient
Este comando crea los archivos de inicio para una aplicación básica "Hola mundo". El nombre del proyecto es "WebAPIClient".
Vaya al directorio "WebAPIClient" y ejecute la aplicación.
cd WebAPIClient
dotnet run
dotnet run
ejecuta automáticamentedotnet restore
para restaurar las dependencias que necesita la aplicación. También ejecutadotnet build
si es necesario. Debería ver la salida de la aplicación"Hello, World!"
. En el terminal, presione Ctrl+C para detener la aplicación.
Realización de solicitudes HTTP
Esta aplicación llama a la API de GitHub para obtener información sobre los proyectos incluidos bajo el paraguas de .NET Foundation. El extremo es https://api.github.com/orgs/dotnet/repos. Para recuperar información, realiza una solicitud HTTP GET. Los exploradores también realizan solicitudes HTTP GET, por lo que puede pegar esa dirección URL en la barra de direcciones del explorador para ver la información que recibirá y procesará.
Use la clase HttpClient para realizar solicitudes HTTP. HttpClient solo admite métodos asincrónicos para sus API de larga duración. Por lo tanto, los pasos siguientes crean un método asincrónico y lo llaman desde el método Main.
Abra el archivo
Program.cs
en el directorio del proyecto y reemplace su contenido por lo siguiente:await ProcessRepositoriesAsync(); static async Task ProcessRepositoriesAsync(HttpClient client) { }
Este código:
- Reemplaza la instrucción
Console.WriteLine
por una llamada aProcessRepositoriesAsync
que usa la palabra claveawait
. - Define un método
ProcessRepositoriesAsync
vacío.
- Reemplaza la instrucción
En la clase
Program
, use un elemento HttpClient para controlar solicitudes y respuestas, reemplazando el contenido por el código de C# siguiente.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 encabezados HTTP para todas las solicitudes:
- Un encabezado
Accept
para aceptar respuestas JSON. - Encabezado de
User-Agent
. Estos encabezados se comprueban mediante el código de servidor de GitHub y son necesarios para recuperar información de GitHub.
- Un encabezado
- Configura encabezados HTTP para todas las solicitudes:
En el método
ProcessRepositoriesAsync
, llame al punto de conexión de GitHub que devuelve una lista de todos los repositorios de la organización de .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:
- Espera la tarea devuelta al llamar al método HttpClient.GetStringAsync(String). Este método envía una solicitud HTTP GET al URI especificado. El cuerpo de la respuesta se devuelve como un elemento String, que está disponible cuando se completa la tarea.
- La cadena de respuesta
json
se imprime en la consola.
Compile la aplicación y ejecútela.
dotnet run
No hay ninguna advertencia de compilación porque
ProcessRepositoriesAsync
ahora contiene un operadorawait
. La salida es una larga presentación de texto JSON.
Deserialización del resultado JSON
Los pasos siguientes convierten la respuesta JSON en objetos de C#. La clase System.Text.Json.JsonSerializer se usa para deserializar JSON en objetos.
Cree un archivo con el nombre Repository.cs y agregue el código siguiente:
public record class Repository(string name);
El código anterior define una clase para representar el objeto JSON devuelto desde la API de GitHub. Usará esta clase para mostrar una lista de nombres de repositorio.
El objeto JSON de un objeto de repositorio contiene docenas de propiedades, pero solo se deserializará la propiedad
name
. El serializador omite automáticamente las propiedades JSON para las que no hay ninguna coincidencia en la clase de destino. Esta característica facilita la creación de tipos que funcionan con solo un subconjunto de campos de un paquete JSON grande.La convención de C# es poner en mayúscula la primera letra de los nombres de propiedad, pero la propiedad
name
comienza aquí con minúscula porque coincide exactamente con lo que hay en JSON. Más adelante verá cómo usar nombres de propiedad de C# que no coinciden con los nombres de propiedad JSON.Use el serializador para convertir JSON en objetos de C#. Reemplace la llamada a GetStringAsync(String) en el método
ProcessRepositoriesAsync
por las líneas siguientes:await using Stream stream = await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos"); var repositories = await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
El código actualizado reemplaza GetStringAsync(String) por GetStreamAsync(String). Este método del serializador usa como origen una secuencia, en lugar de una cadena.
El primer argumento para JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) es una expresión
await
. Las expresionesawait
pueden aparecer prácticamente en cualquier parte del código, aunque hasta ahora solo las ha visto como parte de una instrucción de asignación. Los otros dos parámetros,JsonSerializerOptions
yCancellationToken
, son opcionales y se omiten en el fragmento de código.El método
DeserializeAsync
es genérico, lo que significa es necesario proporcionar argumentos de tipo para el tipo de objetos que se debe crear a partir del texto JSON. En este ejemplo, se va a deserializar a enList<Repository>
, que es otro objeto genérico, System.Collections.Generic.List<T>. La claseList<T>
administra una colección de objetos. El argumento de tipo declara el tipo de objetos almacenados enList<T>
. El argumento de tipo es el registroRepository
, porque el texto JSON representa una colección de objetos de repositorio.Agregue código para mostrar el nombre de cada repositorio. Reemplace las líneas donde pone:
Console.Write(json);
por el siguiente:
foreach (var repo in repositories ?? Enumerable.Empty<Repository>()) Console.Write(repo.name);
Las directivas
using
siguientes deben situarse en la parte superior del archivo:using System.Net.Http.Headers; using System.Text.Json;
Ejecutar la aplicación.
dotnet run
La salida es una lista con los nombres de los repositorios que forman parte de .NET Foundation.
Configuración de la deserialización
En Repository.cs, reemplace el contenido del archivo por el código de C# siguiente.
using System.Text.Json.Serialization; public record class Repository( [property: JsonPropertyName("name")] string Name);
Este código:
- Cambia el nombre de la propiedad
name
aName
. - Agrega JsonPropertyNameAttribute para especificar cómo aparece esta propiedad en el JSON.
- Cambia el nombre de la propiedad
En Program.cs, actualice el código para aplicar el nuevo uso de las mayúsculas de la propiedad
Name
:foreach (var repo in repositories) Console.Write(repo.Name);
Ejecutar la aplicación.
La salida es la misma.
Refactorizar el código
El método ProcessRepositoriesAsync
puede realizar el trabajo asincrónico y devolver una colección de los repositorios. Cambie ese método para devolver Task<List<Repository>>
y mueva el código que escribe en la consola cerca del autor de la llamada.
Cambie la signatura de
ProcessRepositoriesAsync
para devolver una tarea cuyo resultado sea una lista de objetosRepository
:static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
Devuelva los repositorios después de procesar la respuesta 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();
El compilador genera el objeto
Task<T>
para el valor devuelto porque ha marcado este método comoasync
.Modifique el archivo Program.cs, reemplazando la llamada a
ProcessRepositoriesAsync
con lo siguiente para capturar los resultados y escribir cada nombre de repositorio en la consola.var repositories = await ProcessRepositoriesAsync(client); foreach (var repo in repositories) Console.Write(repo.Name);
Ejecutar la aplicación.
La salida es la misma.
Deserialización de más propiedades
En los pasos siguientes se agrega código para procesar más propiedades del paquete JSON recibido. Probablemente no le interesará procesar todas las propiedades, pero si agrega algunas más verá una demostración de otras características de C#.
Reemplace el contenido de la clase
Repository
por la definiciónrecord
siguiente: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);
Los tipos Uri y
int
tienen una funcionalidad integrada para convertir a y desde la representación de cadena. No se necesita código adicional para deserializar desde el formato de cadena de JSON a esos tipos de destino. Si el paquete JSON contiene datos que no se convierten en un tipo de destino, la acción de serialización genera una excepción.Actualice el bucle
foreach
en el archivo Program.cs para mostrar los valores de propiedad: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(); }
Ejecutar la aplicación.
La lista ahora incluye las propiedades adicionales.
Incorporación de una propiedad de fecha
La fecha de la última operación de inserción presenta este formato en la respuesta JSON:
2016-02-08T21:27:00Z
Este es el formato de la hora universal coordinada (UTC), por lo que el resultado de la deserialización es un valor DateTime cuya propiedad Kind es Utc.
Para que una fecha y hora se represente en su zona horaria, debe escribir un método de conversión personalizado.
En Repository.cs, agregue una propiedad para la representación UTC de la fecha y hora y una propiedad
LastPush
de solo lectura que devuelva la fecha convertida a la hora local. El archivo debería tener el siguiente aspecto: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(); }
La propiedad
LastPush
se define utilizando un miembro con forma de expresión para el descriptor de accesoget
. No hay ningún descriptor de accesoset
. La omisión del descriptor de accesoset
es una forma de definir una propiedad read-only en C#. (Sí, puede crear propiedades de solo escritura en C#, pero su valor es limitado).Agregue de nuevo otra instrucción de salida en Program.cs:
Console.WriteLine($"Last push: {repo.LastPush}");
La aplicación completa debe ser similar al archivo Program.cs siguiente:
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(); }
Ejecutar la aplicación.
La salida incluye la fecha y hora de la última inserción en cada repositorio.
Pasos siguientes
En este tutorial, ha creado una aplicación que realiza solicitudes web y analiza los resultados. La versión de la aplicación debe coincidir ahora con el ejemplo terminado.
Obtenga más información sobre cómo configurar la serialización JSON en Procedimiento para serializar y deserializar (calcular referencias y resolver referencias) JSON en .NET.