Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Cómo enviar y recibir JSON con .NET 5 o posterior

Imagen ornamental¿Cómo escribirías código en .NET para llamar a un endpoint o servicio Web que devuelva JSON y procesarlo en tu código...?

Pues bien, necesitas al menos dos funcionalidades de la plataforma .NET:

  1. La capacidad de realizar peticiones HTTP/HTTPS y obtener resultados
  2. El poder recibir y procesar el JSON obtenido, convirtiéndolo en una clase propia para poder manejarla desde .NET

La primera de ellas te la ofrece la clase HttpClient de System.Net.Http, que te proporciona lo necesario para hacer esas peticiones y obtener las correspondientes respuestas en diversos formatos.

El procesamiento de JSON en .NET se ha hecho tradicionalmente con la biblioteca de terceros JSON.NET de NewtonSoft, pero como ya te he contado, en .NET Core 3.0 o posterior tenemos esa funcionalidad integrada en la propia plataforma, y es incluso mejor.

En este artículo te voy a contar cómo puedes llevar a cabo la operación descrita (conectarte a un endpoint, leer lo que devuelve y convertirlo a un objeto .NET) de dos maneras: la "tradicional" con .NET Core y la más moderna y sencilla con .NET 5 o posterior.

Vamos a verlo con un ejemplo...

Una API gratuita para valores de cambio de monedas

Imagina que en tu programa de contabilidad necesitas tener en cuenta el cambio de moneda entre dólares y euros. Para ello decides utilizar el Servicio Web gratuito de valoraciones de cambio que te ofrece Frankfurter. Este servicio actualiza cada día, a eso de las 4 de la tarde (hora de España), las cotizaciones de las diferentes monedas según los valores de cambio publicados por el banco central europeo. Es una API muy sencilla de utilizar y nos viene muy bien para nuestro ejemplo práctico.

Si nos interesa obtener el cambio a día de hoy de dólares a euros, solo tenemos que llamar al siguiente endpoint de la API:

https://api.frankfurter.app/latest?to=USD,EUR

que nos devuelve un resultado en formato JSON con el cambio actual, así:

El JSON resultante de la llamada

Como ves, este objeto en notación JSON nos dice que la cantidad (amount) de la moneda de base (base) es 1 euro, la fecha de la cotización (date) y en una propiedad rates devuelve el valor del cambio a la moneda solicitada, el dólar en este caso, con su identificador (USD). Esto último es poco usable, porque lo devuelve como un subobjeto cuya única propiedad es el nombre de la moneda que nos interesa y su valor es el cambio. Lo hacen así porque puedes pedir el cambio a más de una moneda, pero dificulta un poco (pero poco) su uso desde .NET. Veremos enseguida cómo solucionarlo.

Para manejar todos estos datos en C# vamos a definir una clase que nos sirva para contenerlos, como esta:

internal class InfoCambioMoneda
{
    public double amount { get; set; }
    public string @base { get; set; }
    public DateTime date { get; set; }
    public Dictionary<string, double> rates { get; set; }
}

Fíjate en un par de detalles:

  • base no es un nombre válido para una propiedad o para una variable en C# porque es una palabra reservada que se refiere a la clase base de una relación de herencia entre clases. Podríamos hablarlo solucionado poniéndole otro nombre y un atributo para indicar su verdadero nombre en el JSON, pero es más fácil simplemente ponerle una arroba delante, que en C# se usa precisamente para esto: para forzar que el compilador acepte una palabra reservada como nombre de variable o propiedad. Así que por eso le ves la @ delante.
  • Como el valor de cambio lo devuelve como un objeto con una propiedad llamada USD, la cual contiene un valor decimal para el cambio, podríamos haber definido una clase así y usarla en la propiedad rates. Pero es más directo y flexible usar un diccionario genérico que use claves de cadena y valores double para guardar esta información. De este modo, si en el futuro usamos la misma clase InfoCambioMoneda para obtener más valores de cambio que uno solo (u otras monedas) no tenemos que cambiar el código. El deserializador de .NET (que transoforma de JSON a un objeto) es lo suficientemente inteligente para hacer esa conversión, como veremos enseguida.

Vale. Ahora que tenemos definida la clase que va a guardar los datos de la conversión, vamos al código que se encarga de obtenerlos desde el servicio Web. Para lo cual veremos las dos maneras de hacerlo mencionadas antes.

Conectándonos al servicio y procesando resultados - Método "tradicional"

El siguiente fragmento de código te muestra cómo hacer la operación descrita con código .NET que utiliza las dos funcionalidades mencionadas (conexión y procesamiento de JSON):

internal static async Task<double> CambioDolarEuro_Tradicional()
{
    HttpClient httpClient = new();
    using var httpResponse = await httpClient.GetAsync(apiCambioMonedaUrl, HttpCompletionOption.ResponseHeadersRead);

    httpResponse.EnsureSuccessStatusCode();

    if (httpResponse.Content is null || httpResponse.Content.Headers.ContentType?.MediaType != "application/json")
    {
        return 0;
    }
    else
    {
        string apiRes = await httpResponse.Content.ReadAsStringAsync();
        var infoCambio = JsonSerializer.Deserialize<InfoCambioMoneda>(apiRes);
        return infoCambio.rates["USD"];
    }
}

Vamos a analizarlo:

  • Se crea un objeto HttpClient para poder hacer la petición. Se llama de manera asíncrona al servicio (su dirección, vista antes, la he guardado en una constante apiCambioMonedaUrl) mediante una petición HTTP de tipo GET. Fíjate en que uso la opción HttpCompletionOption.ResponseHeadersRead porque me interesa conocer las cabeceras de la respuesta para ver si lo que me devuelve es JSON u otra cosa (por ejemplo HTML). También fíjate en que le he puesto un using delante para garantizar que, pase lo que pase, al terminar la función se van a liberar los recursos del objeto que hace la petición.
  • Se llama al método httpResponse.EnsureSuccessStatusCode() para garantizar que se ha obtenido una respuesta válida de la petición (generalmente un código HTTP 200). En caso de que no sea así se lanzará una excepción automáticamente.
  • Ahora comprobamos que haya contenido en la respuesta obtenida y que el tipo de datos recibido (su tipo MIME) sea "application/json", que es lo que debemos obtener si lo que se recibe está en ese formato. Si no es el caso, devolvemos un tipo de cambio de 0, que es un valor no válido que podemos detectar desde donde se use esta función. También podríamos haber lanzado una excepción propia.
  • Finalmente, si todo ha ido bien, se leen los datos de la respuesta JSON, que son una cadena de texto, con el método Content.ReadAsStringAsync()* de nuestro objeto httpResponse. Para convertirlos en un objeto del tipo propio InfoCambioMoneda que hemos definido antes, usamos el método Deserialize() de la API de manejo de JSON de .NET, como ya te he explicado en un artículo anterior. Como en la propiedad rates tenemos un diccionario, devolvemos el valor del cambio leyendo la clave "USD" que es el nombre de la moneda que hemos pedido (el dólar estadounidense).

*Nota avanzada: lo más "profesional" y recomendable genéricamente en este tipo de código sería leer el JSON devuelto como un StreamReader (que luego consumiremos directamente desde el serializador JSON), y no como una cadena directamente. El motivo es que los datos internamente se leen como un Stream de bytes por parte del objeto httpClient, y el objeto JsonSerializer ya es capaz de trabajar con los mismos. Así que convertirlo en cadena hace que los tengamos ubicados dos veces en memoria (en el Stream y en el string), por lo que es un gasto tonto de memoria e incluso de rendimiento, que cuanto más grande sea la cadena peor será. Lo cierto es que en la práctica nos da un poco igual y no notaremos la diferencia en la mayor parte de los casos, y además en este caso concreto el JSON devuelto es tan pequeño que aún sería más irrisoria la ganancia. Por eso y por no complicar la cosa más he decidido leerlo directamente como cadena de texto.

El código no es terriblemente complicado ni engorroso. De hecho es bastante sencillo y directo, pero puede serlo mucho más gracias a la API System.Net.Http.Json de .NET 5 o posterior, que combina las dos tareas que hemos realizado en una sola. Vamos a verla.

Conectándonos al servicio y procesando resultados - Método .NET 5 o posterior

Con .NET 5 se incorporó a la plataforma un espacio de nombres llamado System.Net.Http.Json que contiene una serie de clases con métodos extensores para las APIs de manejo de peticiones HTTP, combinándolas con el manejo de JSON.

Y es que este escenario es tan común que tiene todo el sentido del mundo combinar ambas cosas para facilitarnos la vida a los desarrolladores.

Haciendo uso de estos métodos extensores, el código que hemos visto antes quedaría de la siguiente manera:

internal static async Task<double> CambioDolarEuro_Extensores()
{
    HttpClient httpClient = new();
    var infoCambio = await httpClient.GetFromJsonAsync<InfoCambioMoneda>(apiCambioMonedaUrl);
	return infoCambio.rates["USD"];
}

Este código tiene solo 3 líneas (y una de ellas es solo devolver el valor), pero hace exactamente lo mismo que el anterior (con una excepción que ahora comentaré).

Nota: de hecho, este código es más eficiente que el anterior por el motivo que expliqué antes: trabaja ya internamente con el Stream de bytes de la respuesta, sin pasar por una cadena intermedia como he hecho yo. Pero ya sabes que lo hice así antes por simplicidad, no por eficiencia.

El método extensor GetFromJsonAsync es genérico y se encarga de hacer todo el procesamiento para obtener la información pedida directamente en un objeto de la clase que le indiquemos en la parte de la genericidad (entre < y >).

¡Más fácil imposible!

Este código en realidad sí que tiene una diferencia con el anterior en cuanto al resultado y si lo consideramos una caja negra no serían equivalentes. Y es que, en este caso, si no devuelve un contenido válido no obtenemos un 0 como respuesta. Es muy fácil capturar cualquier tipo de excepción para hacerlo, pero es que además podemos capturar excepciones específicas del proceso para actuar en consecuencia, como NotSupportedException en caso de que no devuelva JSON (en cuyo caso devolveríamos un 0), JsonException en caso de que sí devuelva JSON pero no sea válido o un convencional HttpRequestException en caso de que la petición falle por algún motivo.

Enviando datos JSON a un servicio con los métodos extensores

Las extensiones tiene métodos para otras cosas también. Y una de las más comunes después de recibir datos JSON es tener que enviarlos. Generalmente se envían como una petición POST a un servicio Web.

El servicio que estamos utilizando, Frankfurter, lógicamente no permite escribir valores de las cotizaciones, pero imaginemos que nuestro programa informa de la nueva cotización a un servicio propio que la almacena para otras cuestiones (por ejemplo para hacer análisis de su evolución o algo parecido).

Así que para eso habría que tomar un objeto de tipo InfoCambioMoneda y enviarlo por POST a nuestro servicio.

Para centrarnos en cómo hacerlo, no vamos a construir un servicio que reciba los datos, sino que vamos a utilizar un servicio online muy útil para depuración de este tipo de llamadas llamado Request Catcher. Este servicio te permite definir tu propia URL dándole un nombre y empezar a enviarle peticiones del tipo que quieras, logueando todo lo que le llega. En mi caso he creado el endpoint https://campusmvp_json_enviar_recibir.requestcatcher.com/ al que vamos a enviar por POST los datos de cambio que nos interesen y podremos ver si llegan o no y de qué manera. Este URL lo tengo definido en el código de ejemplo en una constante llamada apiPostUrl.

Con estos métodos extensores que combinan HTTP y JSON, el envío de esta información sería tan sencillo como este:

HttpClient httpClient = new();
var postResponse = await httpClient.PostAsJsonAsync<InfoCambioMoneda>(apiPostUrl, infoCambio);
postResponse.EnsureSuccessStatusCode();

En la variable infoCambio tenemos un objeto de tipo InfoCambioMoneda que es el que vamos a enviar. El método extensor PostAsJsonAsync se encarga de todo y, como es genérico, para facilitar la serialización le podemos indicar el tipo de la clase que vamos a enviar.

Nuevamente más fácil imposible.

En el servicio vemos cómo se reciben los datos perfectamente:

Los datos JSOn recibidos en Request Catcher

En resumen

Gracias a los métodos extensores del espacio de nombres System.Net.Http.Json de .NET podemos combinar lo mejor de las clases de manejo de peticiones HTTP con la gestión de los datos que recibimos y enviamos en formato JSON. Nos facilitan enormemente la realización de peticiones de envío y recepción de datos con JSON que son tan comunes en cualquier aplicación hoy en día, hasta el punto de necesitar una única llamada a un método para lograrlo. Aprende a sacarles partido para mejorar tu productividad.

Te dejo el código fuente de ejemplo para Visual Studio 2022 en este ZIP (3,17Kb).

José M. Alarcón Aguín Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José M. Alarcón Aguín

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Comentarios (1) -

excelente! hace rato que buscaba este pieza de código, simple y opera 100%

Responder

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.