Blazor es el nuevo framework de desarrollo para crear SPA que ofrece Microsoft y que está teniendo mucho crecimiento en los últimos tiempos. En este artículo haremos una pequeña demostración de cómo hacer una llamada a un API externa y mostrar los datos recibidos, utilizando Refit de una manera muy sencilla.

Blazor permite crear aplicaciones en el lado del cliente con C#, por lo que consigue que muchos desarrolladores que nos consideramos más de Backend, podamos dar el salto a Full-Stack sin necesidad de aprender otros frameworks como Angular o React. Asímismo, ya existen varias librerías de controles de UI para Blazor, incluso algunas gratuitas como las de Radzen. La última aplicación real que he desarrollado con Blazor la podéis ver en https://www.saboraemocion.com/

No es el objetivo de este post hacer una introducción a Blazor, ya que existe muy buena documentación y tutoriales oficiales que recomiendo seguir. Podéis empezar en https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor

En cambio, voy a aprovechar un proyecto con Blazor para escribir sobre Refit, una librería que nos permite crear automáticamente un cliente para consumir un API REST externa a partir de la definición de un Interface. Refit se encarga del resto: comunicación Http, autenticación, cabeceras, serialización, conversión de tipos, etc. Usaremos Refit para crear un pequeño conversor de moneda, con los datos ofrecidos por la API gratuita https://api.exchangeratesapi.io/ (ver detalles del servicio en https://exchangeratesapi.io/)

El código fuente de ejemplo lo podéis encontrar en https://github.com/sgisbert/blazor-currencyexchange

 

Configuración inicial

Para este ejemplo sencillo, crearemos una aplicación SPA con Blazor Server con .Net 5. También se puede desarrollar con un modelo basado en cliente con Blazor WebAssembly. Podéis ver las diferencias entre ambos modos de hospedaje en https://docs.microsoft.com/aspnet/core/blazor/hosting-models 

Para crear nuestra aplicación, podemos usar el asistente de Visual Studio, o simplemente, siguiendo los pasos del tutorial de Blazor, lanzar el siguiente comando desde PowerShell:

dotnet new blazorserver -o BlazorApp

Y para comprobar que todo funciona, entramos en la carpeta "BlazorApp" que se ha generado y ejecutamos:

dotnet watch run

que compilará y ejecutará la aplicación, y volverá a compilar y reiniciarla cada vez que el código fuente cambie. Para detenerla basta con pulsar Ctrl+C.

Cuando termine de compilar mostrará que está escuchando en http://localhost:5001 y se abrirá el navegador con esa dirección, mostrando la aplicación que acabamos de crear:

Image1.png

 

Añadiendo la lógica de negocio

El objetivo de este ejemplo será listar los valores de cambio de monedas disponibles para el día de hoy. Para ello, haremos una llamada al endpoint https://api.exchangeratesapi.io/latest, que nos devuelve un objeto JSON como este: 

{"rates":{"CAD":1.5307,"HKD":9.4121,"ISK":155.6,"PHP":58.846,"DKK":7.4368,"HUF":358.5,"CZK":25.849,"AUD":1.5445,"RON":4.8758,"SEK":10.033,"IDR":17094.81,"INR":88.0145,"BRL":6.5633,"RUB":89.6089,"HRK":7.578,"JPY":127.81,"THB":36.386,"CHF":1.0851,"SGD":1.6059,"PLN":4.483,"BGN":1.9558,"TRY":8.447,"CNY":7.8318,"NOK":10.2095,"NZD":1.6642,"ZAR":17.7391,"USD":1.2139,"MXN":24.7094,"ILS":3.97,"GBP":0.86508,"KRW":1339.59,"MYR":4.9048},"base":"EUR","date":"2021-02-19"}

Donde tenemos los diferentes precios de cada moneda con respecto al Euro para la última fecha publicada.

Lo primero que haremos será crear un nuevo proyecto en la solución para albergar nuestra lógica de negocio. Esto no es imprescindible para este ejemplo, pero ayuda a mantener las buenas prácticas ;). Para ello, creamos una nueva librería basada en .Net 5 a la solución, y la llamaremos "Domain". En términos de la metodología "Domain Driven Development" (DDD), esta será nuestra capa de Dominio:

Image3.png

El código fuente completo de este paso lo puedes encontrar en GitHub.

Una vez tenemos el nuevo proyecto, empezaremos creando el modelo de datos con el que vamos a trabajar. Para generar el modelo en C#, podemos usar una herramienta como https://app.quicktype.io/, que a partir del JSON anterior nos va a generar una clase completa en C#, incluso con la opción de añadir métodos auxiliares para serializar y deserializar los datos:

image2.png

Añadimos el código generado a la clase Model/Rates.cs (necesitaremos añadir una referencia al paquete de Nuget Newtonsoft.Json):

namespace Domain.Model
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;

    public partial class Rates
    {
        [JsonProperty("rates")]
        public Dictionary<string, double> RateList { get; set; }

        [JsonProperty("base")]
        public string Base { get; set; }

        [JsonProperty("date")]
        public DateTimeOffset Date { get; set; }
    }

    public partial class Rates
    {
        public static Rates FromJson(string json) => JsonConvert.DeserializeObject<Rates>(json, Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this Rates self) => JsonConvert.SerializeObject(self, Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters = {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}

A continuación, usaremos Refit para definir el servicio que realice el acceso a la API. Para ello, primero incluimos el paquete de Nuget Refit.HttpClientFactory en nuestra solución (Nota: en el momento de escribir este ejemplo, usamos la versión 5.1.2 de Refit. Si instalas la versión 6.x puede que no funcione correctamente porque se ha cambiado el motor de serialización por defecto de Newtonsoft.Json a System.Text.Json. Recomiendo instalar la versión de este ejemplo para seguirlo sin problemas, pero usar la última para nuevos desarrollos)

Añadimos el siguiente Interface al proyecto de Domain Services/ICurrencyApi.cs

using Domain.Model;
using Refit;
using System.Threading.Tasks;

namespace Domain.Services
{
    [Headers("Content-Type: application/json")]
    public interface ICurrencyApi
    {
        [Get("/latest")]
        Task<ApiResponse<Rates>> GetLatest();
    }
}

Donde definimos la llamada al endpoint "latest" de la API, que nos va a devolver un objeto "ApiResponse<Rates>", donde ApiResponse contendrá información de la petición (http response code, errores, etc. y el contenido de la misma, que es nuestro objeto de negocio, Rates.

Seguidamente, añadiremos el servicio que hará uso del servicio de Refit, Services/ICurrencyService.cs:

using Domain.Model;
using Refit;
using System.Threading.Tasks;

namespace Domain.Services
{
    public interface ICurrencyService
    {
        Task<ApiResponse<Rates>> GetLatest();
    }
}

y su implementación, Services/CurrencyService.cs:

using Domain.Model;
using Refit;
using System.Threading.Tasks;

namespace Domain.Services
{
    public class CurrencyService : ICurrencyService
    {
        private readonly ICurrencyApi _api;

        public CurrencyService(ICurrencyApi api)
        {
            _api = api;
        }

        public async Task<ApiResponse<Rates>> GetLatest()
        {
            return await _api.GetLatest();
        }
    }
}

Finalmente, añadiremos una clase a nuestra librería que se encargue de inicializar los servicios que hemos creado, para que la inyección de dependencias sea capaz de inicializarlos correctamente, DomainStartup.cs:

using Domain.Services;
using Microsoft.Extensions.DependencyInjection;
using Refit;
using System;

namespace Domain
{
    public static class DomainStartup
    {
        public static void InitDomain(this IServiceCollection services)
        {
            services.AddScoped<ICurrencyService, CurrencyService>();

            services.AddRefitClient<ICurrencyApi>()
                    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.exchangeratesapi.io/"))
                    .SetHandlerLifetime(TimeSpan.FromMinutes(2));
        }
    }
}

En este caso, inicializamos nuestro CurrencyService, y hacemos uso de la extensión AddRefitClient para indicarle a Refit los parámetros de inicialización del API, como la URL base del servicio.

 

Usamos la lógica en la aplicación Blazor

Hasta el momento no hemos modificado para nada la aplicación Blazor que hemos creado al principio. Ahora añadiremos el código para mostrar el listado de monedas y su tipo de cambio.

Lo primero que hacemos es modificar Startup.cs para inicializar los servicios que hemos creado en el Dominio:

using Domain;

...

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();

    services.InitDomain();
}

A continuación, creamos un nuevo componente razor que albergará la página con el listado de monedas, Pages/Currency.razor:

@page "/currency"

@using Domain.Services
@using Domain.Model
@inject ICurrencyApi CurrencyService


<h1>Currency converter</h1>

@if (rates == null)
{
    <p><em>Loading...</em></p>
    <p>@error</p>
}
else
{
    <h3>1 @rates.Base is worth as of @rates.Date.ToString("d"):</h3>
    <table class="table">
        <thead>
            <tr>
                <th>Currency</th>
                <th>Rate</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var rate in rates.RateList)
            {
                <tr>
                    <td>@rate.Key</td>
                    <td>@rate.Value</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private Rates rates;
    private string error;

    protected override async Task OnInitializedAsync()
    {
        var response = await CurrencyService.GetLatest();
        if (response.IsSuccessStatusCode)
        {
            rates = response.Content;
        }
        else
            error = response.Error.Content;
    }
}

En esta página, primero estamos inyectando nuestro servicio de dominio:

@inject ICurrencyApi CurrencyService

Durante la inicialización del componente, OnInitializedAsync(), llamamos al servicio externo para obtener los datos:

var response = await CurrencyService.GetLatest();

Si la llamada ha sido correcta, inicializamos el valor de la variable local rates, que será la que utilicemos para presentar los datos en pantalla:

rates = response.Content;

Por último, vamos a añadir un enlace en el menú a nuestra nueva página, modificando Shared/NavMenu.razor:

<li class="nav-item px-3">
    <NavLink class="nav-link" href="currency">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Currency
    </NavLink>
</li>

Si todo ha ido bien, y seguimos teniendo la aplicación lanzada con "dotnet watch run", volvemos al navegador y veremos la nueva opción en el menú. Si no, lanzamos la aplicación y esperamos a que se cargue en el navegador. Al pulsar sobre el menú, veremos nuestro listado de monedas frente al euro en la fecha de hoy:

Image4.png

 

Conclusiones

  • Blazor se está convirtiendo en un framework muy interesante para el desarrollo de SPA y aplicaciones web dinámicas, al estilo de Angular, React, etc. especialmente para los desarrolladores especializados en backend y C#.

  • Refit es una librería para crear dinámicamente clientes de APIs REST sin ningún esfuerzo, pero muy potente a su vez.

En próximos posts seguiré mostrando algunas funcionalidades de Blazor y Refit, profundizando un poco más en este ejemplo de aplicación calcular los tipos de cambio entre monedas.