La base de casi todos los sistemas actualmente se basa en la comunicación, es muy raro encontrar sistemas completamente aislados de cualquier otro pues para resolver la mayoría de problemas reales se necesita una estrecha intercomunicación con otras partes.

La forma en la que los sistemas se comunican es a través de llamadas entre sus servicios. Es así que a menudo como desarrollador te encontraras con el escenario en el que tu aplicación deba consumir un servicio web.

El modelo más popular de servicios actualmente es el modelo REST, especialmente los servicios que usan JSON como formato para la transmisión de datos. Podemos listar ejemplos como los de facebook, twitter, Office365, Github e incluso Netflix.

Basado en estas premisas, hoy quiero presentarles una librería muy interesante.

Refit

Refit, es una librería inspirada por Square's Retrofit, la que nos permite convertir servicios web REST en interfaces vivas, de forma que podemos modelar el servicio que queremos consumir a través de atributos y Refit se encarga de los detalles de cómo consumirlo apropiadamente.

Veamos, Github expone un servicio para obtener detalles de un usuario de su plataforma, para eso necesitamos realizar una llamada GET a https://api.github.com/users/{user} ,donde user es el nombre de usuario en Github.

Sin Refit, el código para consumir el servicio simplificado podría ser así:

var client = new HttpClient() {BaseAddress = new Uri("https://api.github.com")};

var httpResponse = await client.GetAsync("api/users/" + "jesulink2514");

if (!httpResponse.IsSuccessStatusCode) return null;

var json = httpResponse.Content.ReadAsStringAsync();

var user = JsonConvert.DeserializeObject<User>(json);

Pero con Refit la API de Github que permite obtener detalles de los usuarios podríamos expresarla así:

public interface IGitHubApi
{
    [Get("/users/{user}")]
    Task<User> GetUser(string user);
} 

Y con ello, la clase RestService genera una implementación de IGitHubApi que usa HttpClient para realizar sus llamadas.

var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com");

var octocat = await gitHubApi.GetUser("jesulink2514");

meme de bien

¿Dónde está soportado Refit?

PlataformaEstatus
Xamarin.Android
Xamarin.Mac
Xamarin.iOS 64-bit (Unified API)
Xamarin.iOS 32-bit
.NET 4.5+
Windows Store 8.1+
Windows Phone 8.1 Universal App
.NET Core
.NET Standard 1.3

Actualmente no esta soportado la plataforma de Xamarin.iOS 32 bits.

Empezar a trabajar con Refit

Atributos API

Con Refit, cada método de nuestra interfaz debe tener un atributo Http que nos diga que método usar y cuál es la URL relativa del mismo. Refit incluye 5 atributos: Get, Post, Put, Delete y Head.

[Get("/users/list")]

También podemos especificar parámetros directamente en el atributo:

[Get("/users/list?sort=desc")]

Una URL puede contener bloques que reemplazar dinámicamente en base a los parámetros del método. Un bloque de reemplazo esta encerrado por llaves ( { } ) al mismo estilo de una cadena en String.Format.

En caso el nombre del parámetro no encaje con el nombre en la URL puedes emplear otro atributo de Refit, AliasAs.

[Get("/group/{id}/users")]
Task<List<User>> GroupList([AliasAs("id")] int groupId);

Los parámetros que no son especificados como variables de sustitución en la URL serán usados automáticamente como "query string parameters".

Recuerda que la comparación entre parámetros del método y parámetros en la URL es insensible a mayúsculas.

Cuerpo de la Request(Body)

Solo uno de los parámetros en tu método puede ser usado como el body (cuerpo) de la request usando el atributo body.

[Post("/users/new")]
Task CreateUser([Body] User user);

Esto nos deja 4 posibles interpretaciones que Refit hará en base al tipo del parámetro:

  • Si el tipo es Stream, el contenido será enviado a través de StreamContent.
  • Si el tipo es string, la cadena será usada directamente como el contenido.
  • Si el parámetro tiene el atributo [Body(BodySerializationMethod.UrlEncoded)], el contenido será serailizado via Url-encoding.
  • Para cualquier otro tipo, el objeto será serializado como JSON.

Contenido JSON

Un punto importante que resaltar es que Refit usa Json.NET para serializar/deserializar requests y resposes. Por defecto, Refit utiliza las configuraciones globales de Json.NET, Newtonsoft.Json.JsonConvert.DefaultSettings.

Debido a que esas configuraciones afectan a la aplicación entera suele ser útil aislar las configuraciones para las llamadas a los servicios. Cuando creamos la implementación de nuestra interfaz usando Refit, podemos pasarle opcionalmente un objeto RefitSettings que te permite especificar las configuraciones de socialización para ese cliente API en particular:

var gitHubApi = RestService.For<IGitHubApi>("https://api.github.com",
    new RefitSettings {
        JsonSerializerSettings = new JsonSerializerSettings {
            ContractResolver = new SnakeCasePropertyNamesContractResolver()
        }
    });

var otherApi = RestService.For<IOtherApi>("https://api.example.com",
     new RefitSettings {
         JsonSerializerSettings = new JsonSerializerSettings {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        }
    });

Como podrán suponer en este punto, si, la serializacion/deserializacion puede personalizarse usando el atributo JsonProperty de Json.NET:

public class Foo 
{
    // Funciona como [AliasAs("b")]
    [JsonProperty(PropertyName="b")] 
    public string Bar { get; set; }
}

Post de Formulario

Para APIs que toman POST request de formularios por ejemplo los que son serializados como application/x-www-form-urlencoded, debemos inicializar el atributo Body con BodySerializationMethod.UrlEncoded.

Así el parámetro puede ser un diccionario:

public interface IMeasurementProtocolApi
{
    [Post("/collect")]
    Task Collect([Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, object> data);
}

var data = new Dictionary<string, object> {
    {"v", 1}, 
    {"tid", "UA-1234-5"}, 
    {"cid", new Guid("d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c")}, 
    {"t", "event"},
};

// Serialized as: v=1&tid=UA-1234-5&cid=d1e9ea6b-2e8b-4699-93e0-0bcbd26c206c&t=event
await api.Collect(data);

Cargas de tipo Multipart

Métodos que decoremos con el atributo Multipart será enviado con el tipo de contenido(content-type) multipart. Actualmente, están soportados los siguientes tipos:

  • Array de bytes (byte[])
  • Stream, y
  • FileInfo

En el caso del array de bytes y Stream, debemos usar el parámetro AttachmentName para especificar el nombre para el adjunto. Usando FileInfo, el nombre de archivo será usado.

public interface ISomeApi
{
    [Multipart]
    [Post("/users/{id}/photo")]
    Task UploadPhoto(int id, [AttachmentName("photo.jpg")] Stream stream);
}

Obteniendo la respuesta

Otro punto importante que resaltar es que con Refit no hay opción para requests síncronas, todas las request deberán ser asíncronas.

Así como contenido del body cambia a través de los tipos de parámetros, el tipo de respuesta también lo hace.

Retornando una tarea sin un parámetro de tipo descartara el contenido y solo te dirá si la llamada se completó exitosamente o no:

[Post("/users/new")]
Task CreateUser([Body] User user);

// lanzara una excepción si la llamada falla
await CreateUser(someUser);

Si el parámetro de tipo es 'HttpResponseMessage' or 'string', la respuesta sin procesar o el contenido sin procesar como string será devuelto.

// Retorna el contenido como un string (i.e. JSON)
[Get("/users/{user}")]
Task<string> GetUser(string user);

// Retorna la respuesta sin procesar
[Get("/users/{user}")]
Task<HttpResponseMessage> GetUser(string user);

Cabe resaltar, que el alcance de este post no cubre el uso de HEADERS, característica que está disponible en Refit y permite abordar escenario de APIs con seguridad por ejemplo.

Consumir apis REST con .NET es sencillo

Ahora que hemos revisado la base de Refit, veámoslo en acción en una súper rápida demo.

Para esta demo usaremos la API de OMDB ( http://www.omdbapi.com/ ) que nos da acceso a detalles de películas.

Si vemos la documentación de la API, podemos ver que tiene un largo número de parámetros cada uno con opciones y significado a pesar de su nombre corto criptográfico como t para title o y para año.

Asi que expresaremos nuestra API de la siguiente forma:

public interface IMoviesApi
{
    [Get("/")]
    Task<Movie> FindMovies(
        [AliasAs("i")]string id,
        [AliasAs("t")]string title,
        MovieType? type, 
        [AliasAs("y")]int? year,
        PlotType? plot);
}

public enum PlotType
{
    Short,
    Full
}

public enum MovieType
{
    movie,
    series,
    episode
}

Podemos resaltar el uso de AliasAs para que nuestra interfaz tenga nombres de parámetros con mas significado para nuestros consumidores pero que cuando sea utlizado por Refit puede transformarlos a como la API los necesita.

Para completar la definición debeos de crear la clase que represente la respuesta en JSON que devuelve el servicio, para eso tomaremos como base una respuesta habitual, pueden probar accediendo a http://www.omdbapi.com/?t=La+la+land.

Copiamos el contenido que devuelve el servicio y nos dirigimos a Visual Studio. Usaremos una característica poco conocida que nos permite pegar un objeto JSON como una clase. Edit > Paste special > Paste JSON as classes

Renombramos la clase de RootObject a Movie y tendremos listo la definición de la interfaz.

Con la definición de nuestra interfaz lista no necesitamos nada mas para empezar a consultar el servicio.

 var moviesClient = RestService.For<IMoviesApi>("http://www.omdbapi.com",
       new RefitSettings
       {
           JsonSerializerSettings = new JsonSerializerSettings {Converters = {new StringEnumConverter()}}
       });

Recordemos que el método es asíncrono así que para que podamos llamarlo de una manera síncrona, solo para efectos de demostración deberemos acceder a la propiedad Result del objeto Task que devuelve nuestro cliente.

 var r = moviesClient.FindMovies(null,title, null, null,PlotType.Full).Result;

Y así de sencillo podemos consultar APIs REST con Refit, ahora veamos todo el código junto de esta pequeña demo que es una aplicación de consola.

Demo

Conclusiones

En .NET tenemos un sin número de componentes muy útiles que forman parte de este excelente ecosistema y Refit no es la excepción, les recomiendo considerarlo a la hora de tener la necesidad( y de hecho la tendrás ) de consumir servicios web REST.

Pueden revisar el código fuente de la demo en Github https://github.com/jesulink2514/DemoRefit.